concepts 和 requires¶
这2个关键字的出现可以很好的限制模板参数中参数类型。在此之前,我们对模板参数类型的限制只能在内部处理,现在通过concepts
和 requires
可以限制参数,并在编译期判断模板参数是否符合限制要求。
1 requires¶
requires的语法形式主要2种,分别是Requires clauses(requires 子句)和Requires expressions(requires 表达式)
1.1 requires 子句¶
- 语法规则:
- 初等表达式,例如
Swappable<T>、std::is_integral<T>:: value、( std::is_object_v<Args> && ...)
或任何带括号表达式 - 以运算符 && 联结的初等表达式的序列
- 以运算符 || 联结的前述表达式的序列
- 注意:关键词
requires
必须后随某个常量表达式(故可以写为 requires true),因此才能实现编译期判断 - 示例
#include <type_traits>
#include <iostream>
template<typename T> // (1)
concept Integral = std::is_integral<T>::value;
template<typename T> // (2)
requires std::is_integral<T>::value
T gcd(T a, T b){
if( b == 0 ) return a;
else return gcd(b, a % b);
}
template<typename T> // (2)
requires Integral<T>
T gcd1(T a, T b){
if( b == 0 ) return a;
else return gcd1(b, a % b);
}
template<typename T> // (2)
T gcd2(T a, T b)requires Integral<T>{
if( b == 0 ) return a;
else return gcd2(b, a % b);
}
template<Integral T> // (2)
T gcd3(T a, T b){
if( b == 0 ) return a;
else return gcd3(b, a % b);
}
template<typename T> // (5)
requires Integral<T>&&(sizeof(T)==4)
T gcd4(T a, T b){
if( b == 0 ) return a;
else return gcd4(b, a % b);
}
int main(){
std::cout << std::endl;
std::cout << "gcd(100, 1)= " << gcd(100l, 1l) << std::endl;
std::cout << "gcd1(100, 2)= " << gcd1(100, 2) << std::endl;
std::cout << "gcd2(100, 3)= " << gcd1(100, 3) << std::endl;
std::cout << "gcd3(100, 4)= " << gcd1(100, 4) << std::endl;
std::cout << "gcd4(100, 5)= " << gcd4(100, 5) << std::endl;
}
1.2 requires 表达式¶
- 语法规则
requires ( 形参列表(可选) ) { 要求序列 }
- 要求序列包含4种类型
- 简单要求(simple requirement)
- 类型要求(type requirement)
- 复合要求(compound requirement)
- 嵌套要求(nested requirement)
- 注意:因为要求序列中的表达式是不求值的,只是检查表达式是否合法,因此,如
1.2.1 简单要求¶
- 规则:要求序列是不含
requires
关键字的表达式语句,如表达式是a+b;
,而不能是requires (a+b);
. - 注意:它断言该表达式合法。该表达式是不求值操作数;只检查语言正确性。而且表达式可以有多个,之间用分号
;
隔开。 - 示例
template<typename T>
concept Addable =
requires (T a, T b) {
a + b; // “表达式 a + b 是可编译的合法表达式”
};
template <class T, class U = T>
concept Swappable = requires(T&& t, U&& u) {
swap(std::forward<T>(t), std::forward<U>(u));
swap(std::forward<U>(u), std::forward<T>(t));
};
1.2.2 类型要求¶
- 规则:要求
typename
关键字后接类型名,用来判断指定类型名是否合法
#include <type_traits>
#include <vector>
int main(){
static_assert(
requires(std::vector<int> v){
typename std::vector<int>::value_type; //Type Requirement
}
);
1.2.3 复合要求¶
- 语法:
{ 表达式 } noexcept(可选) ->返回类型要求(可选) ;
- 示例
template<typename T> concept C2 =
requires(T x) {
{*x} -> std::convertible_to<typename T::inner>; // 表达式 *x 必须合法
// 并且 类型 T::inner 必须合法
// 并且 *x 的结果必须可以转换为 T::inner
{x + 1} -> std::same_as<int>; // 表达式 x + 1 必须合法
// 并且 std::Same<decltype((x + 1)), int> 必须被满足
// 亦即,(x + 1) 必须为 int 类型的纯右值
{x * 1} -> std::convertible_to<T>; // 表达式 x * 1 必须合法
// 并且其结果必须可以转换为 T
};
1.2.4 嵌套要求¶
- 语法:
requires 约束表达式 ;
- 示例
#include <type_traits>
#include <vector>
int main(){
static_assert(
requires(std::vector<int> v, int i){
v.at(i); //Simple Requirement
typename std::vector<int>::value_type; //Type Requirement
(sizeof(int)==5); //这个结果是false,表达式合法,依然检查通过
requires (sizeof(int)==5); //嵌套要求,这个结果为false,约束不能通过
}
);
2 concepts¶
concepts
是约束的集合,概念有2种形式,以函数模板定义(称为函数概念),以变量模板定义(称为变量概念)。
2.1 变量概念¶
- 语法:
template < 模板形参列表 > concept 概念名 = 约束表达式;
- 规则:
- 约束表达式可以
requires
- 必须有类型 bool
- 不允许 constexpr ,变量自动为 constexpr
- 示例
// 来自标准(范围 TS )的变量概念
template <class T, class U>
concept bool Derived = std::is_base_of<U, T>::value//满足概念库中定义的T是U的基类
&&requires(T t){++t;}//t满足++运算
||(sizeof(T)>4); //T类型大小大于4
2.2 函数概念¶
- 规则
- 不允许 inline 与 constexpr ,函数自动为 inline 与 constexpr
- 不允许 friend 与 virtual
- 不允许异常规定,函数自动为 noexcept(true) 。
- 不能声明并延迟定义,不能重声明
- 返回类型必须是 bool
- 不允许返回类型推导
- 参数列表必须为空
- 函数体必须仅由一条 return 语句组成,其参数必须是一条制约表达式(谓词制约、其他制约的
- 合取/析取或 requires 表达式,见后述)
// 来自标准(范围 TS )的函数概念
template <class T>
concept bool EqualityComparable() {
return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; };
}
2.3 使用概念¶
有三种使用概念的方式
//Requires Clause
template<typename Cont>
requires Sortable<Cont>
void sort(Cont& container);
//Trailing Requires Clause
template<typename Cont>
void sort(Cont& container) requires Sortable<Cont>;
//Constrained Template Parameters
template<Sortable Cont>
void sort(Cont& container);