【C++20】概念约束

概念约束

定义概念

concept : 它是一个对类型约束的编译期谓词,给定一个类型判断其是否能满足语法和语义要求
符号C<T>中的C就是概念,T是一个类型,它表达"如果T满足C的所有要求,那么为真,否则为假"
C++中定义一个concept的语法为:

template<被约束的参数列表>
concept 概念名 = 约束表达式;

概念被定义为约束表达式,也可以理解成布尔常量表达式

template<typename T>
concept integral = is_integral_v<T>;
template<typename T>
concept floating_point = is_floating_point_v<T>;

概念和模板using的别名很类似,前者是对布尔类型表达式的别名,而后者是对模板类型的别名,它们都不允许自身进行实例化或特例化

在判断类型是否满足概念时,编译器将会对概念定义的约束表达式进行求值,如果在定义概念时表达式类型不为bool类型,将引发一个编译错误

//atomic constraint must be of type 'bool'(fuond 'int')
template<typename T>concept Foo = 1

可以运用与(&&)或(||)来表达合取和析取

template<typename T>
concept C = is_integral_v<typename T :: type> || (sizeof(T) > 1);
static_assert(C<double>);

对于可变参数模板形成的约束表达式,既不是约束合区也不是约束析取

template<typename...Ts>
concept C = (is_integral_v<typename Ts :: type> || ...)

它首先检查整个表达式是否合法,只要有一个模板参数没有类型成员type,整个表达式将为假

requires表达式

除了使用type traits来定义概念,requires表达式也提供了一种简明的方式来表达对模板参数及其对象的特征要求,requires表达式语法为

requires (可选的参数列表){
    一系列表达式(要求)
}
  1. requires表达式的结果为bool类型,即编译时谓词.表达式体应至少提出一条要求,在表达式体内处于不求值环境
  2. 可选的形参列表声明了一系列局部变量,这些局部变量不允许提供默认参数,它们对整个表达式体可见
  3. requires表达式提供了四种形式的要求:简单要求,类型要求,复合要求和嵌套要求

简单要求

template<typename M>
concept Machine = requires(M m){
    m.powerUp();    //需要存在成员函数powerUp
    m.powerDown();  //需要存在成员函数powerDown
};

它声明了模板参数M的局部对象m,然后在表达式体内提出了两个要求
约束求值的过程中并不会取创建对象,也不会进行接口调用,仅仅以及表达式是否合法来确认是否满足要求

template<typename T>
concept Animal = requires(T animal){
    play(animal);    //需要存在自由函数animal
    T::count;       //需要存在静态成员count
    animal.age;     //需要存在成员变量age
};
template<typename T>
concept Number = requires(T a , T b , T c){
    a == a;     //要求对象能进行判等操作
    a + b * c;      //要求对象能进行加/乘操作
};//只会检查表达式的合法性,不会进行真正的计算

类型要求

template<typename T>
concept C = requires {
    typename T ::type;      //要求存在成员类型type
    typename vector<T>;     //要求能与vector结合,能够模板实例化
};
struct Foo {using type = float};
static_assert(C<Foo>);

这段代码中的requires无需引入局部变量,直接对类型提出要求即可.表达式中使用typename关键字来表达它是一个类型要求

复合要求

希望表达式的类型能符合要求,例如要求函数返回类型为int,不会抛异常等
复合要求的语法

{表达式}可选的noexcept 可选的返回类型概念要求

复合要求需要用大括号将表达式括起来

如果要求表达式不能抛异常,这时候noexcept关键字就派上了用场

template<typename T>
concept Moveable = requires (T a , T b){
    {a = std :: move(b)}noexcept;   //要求对象间的移动禁止抛异常
};

<concepts>提供了两个概念same_as和convertible_to
借助这两个概念,我们可以定义如下概念

template<typename T>
concept C = requires (T x){
    {f(x)} -> same_as<T>;       //要求f(x)的返回类型与T一致
    {g(x)} -> convertible_to<double>;   //要求g(x)的返回类型能够转换成double
};

concept会将表达式的类型补充到概念的第一个参数
使用 -> 来表达对表达式的类型要求,后面紧接着的是需要满足的概念

嵌套要求

在表达式体中通过requires链接一个编译时常量谓词来表达额外的约束,有如下形式

  1. type triats
  2. concept
  3. requires表达式
  4. constexpr 值或函数
template<typename T>
concept C = requires {//要求用编译期谓词
    requires sizeof(T) > sizeof(void *);
    requires is_trivial_v<T>;
};

注意事项

requires表达式为编译时谓词,它不一定需要在concept定义的时候出现,只要是能接受布尔表达式的地方都允许它的存在

template<typename T>
constexpr bool has_number_swap = requires(T a , T b){
    a.swap(b);
};

考虑如下两种差异

requires {
    布尔表达式      //只检查表达式的合法性
    requires 布尔表达式     //在合法性基础上求值
}

如果写了如下代码

requires {//永远满足
    sizeof(T) <= sizeof(int);
};

这仅仅是检查表达式的合法性,对sizeof的结果进行比较是永远满足的要让判断T的大小小等于int的大小应该

requires {
    requires sizeof(T) <= sizeof(int);
};

requires子句

使用requires子句可以为一个模板类或者模板函数添加约束

template<typename T>
requires is_integral_v<T>
T gcd(T a , T b);

模板头中的requires子句表达了模板参数应该在什么条件下工作,同样地,它还可以接受一个约束表达式

如果使用requires子句结合requires表达式来实现将更加合理

template<typename T>
requires requires(T obj){obj.OnInit();}
void f(T & obj){
    std::cout << "1" << endl;
    return obj.OnInit();
}
template <typename T>
void f(T& ){std::cout << "2" << srd::endl;}

如果用户提供的类型有成员函数OnInit,那么两个函数都可行,根据标准,受约束的更优,编译器将选择正确的第一个版本

注意:考虑如下代码

constexpr bool p(int){return true;}
template <typename T>
requires p(0) //语法错误,需要将p(0)通过括号括起来
void f(T){};

编译器在解析这段代码时遇到约束p(0)会认为这是一个类型转换表达式,将数值类型转换成其他类型p,然而实际上表达的是一个谓词函数的调用,这时候需要通过括号将p(0)括起来

除了通过requires子句引入约束之外,简单情况下可以通过更简洁的语法来引入约束

template<integral T , integral U> // requires(integrel<T> && integral<U>)
void f(T , U);

关键字typename被替换成了概念integral,对多个模板参数添加约束将产生一个约束合取表达式

C++20模板函数的参数可以使用auto来简化,并同时支持添加约束

void f(integral auto a , intehral auto b);

泛型lambda也能通过使用概念进行约束

auto f = [](integral auto lhs , integral auto rhs)
            {return lhs * rhs;};

当对模板类型进行显式实例化时,若受约束的成员函数不负荷要求,编译器将不为这个函数生成代码,这是enable_if做不到的地方

约束的偏序规则

如果两个版本同样含有约束切都满足,哪个更优呢?

1.将约束展开为原子约束的合取与析取表达式

若为简单的约束包含关系,如果它们拥有包含关系,若P包含Q而Q不包含P,则P比Q更优;反之,Q比P更优
即约束的合取形式R ∧ \wedge S要比R更优,而析取形式R要比R ∨ \vee S更优

概念标准库<concepts>

C++20标准库为<concepts>提供了一些基本的概念

  1. same_as (与某类相同) same_as概念要求输入两个类型参数判断是否满足相同的约束
  2. derived_from (派生自某类) 表达两个类之间是否存在is-a关系
  3. convertible (可转换为某类) 一个表达式可显式或隐式转换成目标类型
  4. 算术概念
  5. 值概念
  6. invocable (可调用的)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值