一文读懂C++20新特性之概念、约束(concept, constraint)

约束与概念是C++20中最新引入的核心语言特性。约束(constraint)可以关联到类模板、函数模板、类模板成员函数,指定了对模板实参的一些要求,这些要求可以被用于选择最恰当的函数重载和模板特化。概念(concept) 是这些要求(即约束)的集合。

概念(concept)

语法:

template < 模板形参列表 >
concept 概念名 = 约束表达式;

例子:

template<class T, class U>
concept isChildOf = std::is_base_of<U, T>::value;//类型约束, T必须继承自U


/***
    使用概念
    注意:概念在类型约束中接受的实参要比它的形参列表要求的要少一个,
    因为按语境推导出的类型会隐式地作第一个实参
***/
template<isChildOf<Base> T>
void f(T); // T 被 isChildOf<T, Base> 约束

组成概念的约束表达式也可以用requires字句定义:

//以下代码摘自cppreference: https://en.cppreference.com/w/cpp/language/constraints

#include <concepts>
 
// 概念 "Hashable" 的声明可以被符合以下条件的任意类型 T 满足:
// 对于 T 类型的值 a,表达式 std::hash<T>{}(a) 可以编译并且它的结果可以转换到 std::size_t
template<typename T>
concept Hashable = requires(T a)
{
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
 
//在函数模板中使用概念 
template<Hashable T>
void f(T); // 受约束的 C++20 函数模板

也可以按如下格式使用概念:

template<typename T> requires Hashable<T> //requires子句放在template<>之后
void f(T) 
{
    //...
}

template<typename T>
void f(T) requires Hashable<T>  //requires子句放在函数参数列表之后
{
    //...
}

注意:

概念本身不能被约束,概念不能被递归定义。

//以下代码摘自cppreference: https://en.cppreference.com/w/cpp/language/constraints
template<typename T>
concept V = V<T*>; // 错误:递归的概念
 
template<class T>
concept C1 = true;
template<C1 T>
concept Error1 = true; // 错误:C1 T 试图约束概念定义
template<class T> requires C1<T>
concept Error2 = true; // 错误:requires 子句试图约束概念

requires关键字

1. requires关键字可以用来引入require子句

        在这种情况下,requires后面必须跟随一个常量表达式,或者满足如下形式的requires表达式:  

  • 初等表达式,例如 Swappable<T>、std::is_integral<T>::value、(std::is_object_v<Args> && ...) 或任何带括号的表达式
  • 以运算符 && 联结的初等表达式的序列
  • 以运算符 || 联结的前述表达式的序列
template<class T>
constexpr bool is_meowable = true;
 
template<class T>
constexpr bool is_purrable() { return true; }
 
template<class T>
void f(T) requires is_meowable<T>; // OK
 
template<class T>
void g(T) requires is_purrable<T>(); // 错误:is_purrable<T>() 不是初等表达式
 
template<class T>
void h(T) requires (is_purrable<T>()); // OK

2. requires关键字也用来开始一个 requires 表达式

此时,requires表达式是bool类型的纯右值表达式,描述对一些模板实参的约束。这种表达式在约束得到满足时是true,否则是false,比如下面的代码:

template<typename T>
concept Addable = requires (T x) { x + x; }; // requires 表达式
 
template<typename T> requires Addable<T> // requires 子句,不是 requires 表达式
T add(T a, T b) { return a + b; }
 
template<typename T>
    requires requires (T x) { x + x; } // 随即的约束,注意关键字被使用两次
T add(T a, T b) { return a + b; }

require表达式具有如下语法:

requires { 要求序列 }		
requires ( 形参列表(可选) ) { 要求序列 }

其中,要求序列根据复杂程度可以分为以下四种:

  • 简单要求(simple requirement)
  • 类型要求(type requirement)
  • 复合要求(compound requirement)
  • 嵌套要求(nested requirement)
// 简单要求
template<typename T>
concept Addable = requires (T a, T b)
{
    a + b; // “表达式 a + b 是可编译的合法表达式”
};

// 类型要求
template<typename T>
using Ref = T&;
 
template<typename T>
concept C = requires
{
    typename T::inner; // 要求的嵌套成员名
    typename S<T>;     // 要求的类模板特化
    typename Ref<T>;   // 要求的别名模板替换
};

// 嵌套要求
template <class T>
concept Semiregular = DefaultConstructible<T> &&
    CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
requires(T a, size_t n)
{  
    requires Same<T*, decltype(&a)>; // 嵌套:“Same<...> 求值为 true”
    { a.~T() } noexcept; // 复合:"a.~T()" 是不抛出的合法表达式
    requires Same<T*, decltype(new T)>; // 嵌套:“Same<...> 求值为 true”
    requires Same<T*, decltype(new T[n])>; // 嵌套
    { delete new T }; // 复合
    { delete new T[n] }; // 复合
};

复合要求有自己的语法,因此我们将它单列出来,其语法形式为:

{ 表达式 } noexcept(可选) 返回类型要求(可选) ;		
返回类型要求	-> 类型约束

例子:

// 复合要求
template<typename T>
concept C2 = requires(T x)
{
    // 表达式 *x 必须合法
    // 并且类型 T::inner 必须合法
    // 并且 *x 的结果必须可以转换为 T::inner
    {*x} -> std::convertible_to<typename T::inner>;
 
    // 表达式 x + 1 必须合法
    // 并且 std::Same<decltype((x + 1)), int> 必须被满足
    // 也就是说,(x + 1) 必须是 int 类型的纯右值
    {x + 1} -> std::same_as<int>;
 
    // 表达式 x * 1 必须合法
    // 并且它的结果必须可以转换到 T
    {x * 1} -> std::convertible_to<T>;
};

约束的影响

当编译器在进行模板函数的重载决议时,会选择更受约束的版本

template<typename T>
concept Decrementable = requires(T t) { --t; };
template<typename T>
concept RevIterator = Decrementable<T> && requires(T t) { *t; };
 
// RevIterator 能归入 Decrementable,但反之不行
 
template<Decrementable T>
void f(T); // #1
 
template<RevIterator T>
void f(T); // #2,比 #1 更受约束
 
f(0);       // int 只满足 Decrementable,选择 #1
f((int*)0); // int* 满足两个约束,选择 #2,因为它更受约束

本文只是列举了“概念”,“约束”等新特性的表面用法,笔者自身水平还不能做更深入讨论,如有遗漏,欢迎广大网友补充,批评指正。

本文参考自:Constraints and concepts (since C++20) - cppreference.com

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值