原文详见:C++20: Concepts, the Details
在我的上一篇文章 C++20:两个极端和概念的救赎中,我给出了概念的第一个动机:概念对模板参数进行语义约束。今天,我将以紧凑的形式来介绍概念的不同用例。
细节
只要记住这一点:概念的优点是什么?
- 约束(requires)将变为模板接口的一部分而存在
- 函数的重载或类模板的特化可以基于概念
- 因为编译器能够将模板参数的约束与实际模板参数相比较,从而使我们能得到更加友好的报错信息
- auto 和概念的用法统一到了一起。你可以不使用 auto,而是使用概念
- 如果函数声明使用了概念,它将自动成为函数模板。因此,编写函数模板与将与编写函数一样容易
这篇文章是关于前三点的。让我们来看看概念的许多不同用法:
三种方式
有三种使用可排序概念的方式。为了简单起见,我仅显示函数模板的声明。
制约条件
template<typename Cont>
requires Sortable<Cont>
void sort(Cont& container);
尾随制约条件
template<typename Cont>
void sort(Cont& container) requires Sortable<Cont>;
受制约的模板参数
template<Sortable Cont>
void sort(Cont& container)
在这种情况下,算法排序要求容器是可排序的。 Sortable 必须是一个常量表达式和一个谓词。
类
你可以定义仅接受对象的类模板。
template<Object T>
class MyVector{};
MyVector<int> v1; // OK
MyVector<int&> v2; // ERROR: int& does not satisfy the constraint Object
编译器会抱怨引用不是对象。也许您想知道,什么是对象? std::is_object 的实现 -- 类型萃取函数可能已经给出了答案:
template< class T>
struct is_object : std::integral_constant<bool,
std::is_scalar<T>::value ||
std::is_array<T>::value ||
std::is_union<T>::value ||
std::is_class<T>::value> {};
对象可以是标量,也可以是数组,也可以是联合或类。
成员函数
template<Object T>
class MyVector{
...
void push_back(const T& e) requires Copyable<T>{}
...
};
在这种情况下,成员函数要求模板参数T必须是可复制的(Copyable)。
可变参数模板
// allAnyNone.cpp
#include <iostream>
#include <type_traits>
template<typename T>
concept Arithmetic = std::is_arithmetic<T>::value;
template<Arithmetic... Args>
bool all(Args... args) { return (... && args); }
template<Arithmetic... Args>
bool any(Args... args) { return (... || args); }
template<Arithmetic... Args>
bool none(Args... args) { return !(... || args); }
int main(){
std::cout << std::boolalpha << std::endl;
std::cout << "all(5, true, 5.5, false): " << all(5, true, 5.5, false) << std::endl;
std::cout << "any(5, true, 5.5, false): " << any(5, true, 5.5, false) << std::endl;
std::cout << "none(5, true, 5.5, false): " << none(5, true, 5.5, false) << std::endl;
}
你可以在可变参数模板中使用概念。函数模板的定义基于折叠表达式。all、any 和 none 都需要类型参数 T 来支持 Arithmetic(算术) 概念。Arithmetic 概念的本质意味着 T 不是整数就是浮点数。
多个要求
当然,你可以对模板参数使用多个要求。
template <SequenceContainer S,
EqualityComparable<value_type<S>> T>
Iterator_type<S> find(S&& seq, const T& val){
...
}
函数模板的 find 要求容器 S 是SequenceContainer(序列容器),并且其元素是EqualityComparable(可相等比较的)。
重载
std::advance(iter, n) 会增加给定的迭代器 iter 以 n 个元素的步长。根据迭代器的不同,实现可以使用指针算术,也可以依次执行 n 次增加。在第一种情况下,执行时间是常数;在第二种情况下,执行时间取决于步骤大小 n。由于概念,您可以针对迭代器的不同种类将 std::advance 重载。
template<InputIterator I>
void advance(I& iter, int n){...}
template<BidirectionalIterator I>
void advance(I& iter, int n){...}
template<RandomAccessIterator I>
void advance(I& iter, int n){...}
// usage
std::vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto vecIt = vec.begin();
std::advance(vecIt, 5); // RandomAccessIterator
std::list<int> lst{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto lstIt = lst.begin();
std::advance(lstIt, 5); // BidirectionalIterator
std::forward_list<int> forw{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto forwIt = forw.begin();
std::advance(forwIt, 5); // InputIterator
根据迭代器种类的不同,对容器 std::vector,std::list 和 std :: forward_list 的支持将匹配到最合适的 std :: advance 实现。
特化
概念还支持模板特化
template<typename T>
class MyVector{};
template<Object T>
class MyVector{};
MyVector<int> v1; // Object T
MyVector<int&> v2; // typename T
- MyVector<int&> 匹配到不受约束的模板参数
- MyVector<int> 匹配到受约束的模板参数
接下来?
我的下一篇文章是关于 C++20 的语法统一。使用 C++20,您可以在每个地方使用受约束的占位符(concept),或者以 C++11 的方式使用不受约束的占位符(auto)。但这并不是统一的结束。用 C++20 定义模板变得非常简单,只需在函数的声明中使用带约束或不带约束的占位符即可。
网友评论