当前位置:   article > 正文

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

c++ 20 concept

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

概念(concept)

语法:

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

例子:

  1. template<class T, class U>
  2. concept isChildOf = std::is_base_of<U, T>::value;//类型约束, T必须继承自U
  3. /***
  4. 使用概念
  5. 注意:概念在类型约束中接受的实参要比它的形参列表要求的要少一个,
  6. 因为按语境推导出的类型会隐式地作第一个实参
  7. ***/
  8. template<isChildOf<Base> T>
  9. void f(T); // T 被 isChildOf<T, Base> 约束

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

  1. //以下代码摘自cppreference: https://en.cppreference.com/w/cpp/language/constraints
  2. #include <concepts>
  3. // 概念 "Hashable" 的声明可以被符合以下条件的任意类型 T 满足:
  4. // 对于 T 类型的值 a,表达式 std::hash<T>{}(a) 可以编译并且它的结果可以转换到 std::size_t
  5. template<typename T>
  6. concept Hashable = requires(T a)
  7. {
  8. { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
  9. };
  10. //在函数模板中使用概念
  11. template<Hashable T>
  12. void f(T); // 受约束的 C++20 函数模板

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

  1. template<typename T> requires Hashable<T> //requires子句放在template<>之后
  2. void f(T)
  3. {
  4. //...
  5. }
  6. template<typename T>
  7. void f(T) requires Hashable<T> //requires子句放在函数参数列表之后
  8. {
  9. //...
  10. }

注意:

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

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

requires关键字

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

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

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

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

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

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

require表达式具有如下语法:

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

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

  • 简单要求(simple requirement)
  • 类型要求(type requirement)
  • 复合要求(compound requirement)
  • 嵌套要求(nested requirement)
  1. // 简单要求
  2. template<typename T>
  3. concept Addable = requires (T a, T b)
  4. {
  5. a + b; // “表达式 a + b 是可编译的合法表达式”
  6. };
  7. // 类型要求
  8. template<typename T>
  9. using Ref = T&;
  10. template<typename T>
  11. concept C = requires
  12. {
  13. typename T::inner; // 要求的嵌套成员名
  14. typename S<T>; // 要求的类模板特化
  15. typename Ref<T>; // 要求的别名模板替换
  16. };
  17. // 嵌套要求
  18. template <class T>
  19. concept Semiregular = DefaultConstructible<T> &&
  20. CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
  21. requires(T a, size_t n)
  22. {
  23. requires Same<T*, decltype(&a)>; // 嵌套:“Same<...> 求值为 true”
  24. { a.~T() } noexcept; // 复合:"a.~T()" 是不抛出的合法表达式
  25. requires Same<T*, decltype(new T)>; // 嵌套:“Same<...> 求值为 true”
  26. requires Same<T*, decltype(new T[n])>; // 嵌套
  27. { delete new T }; // 复合
  28. { delete new T[n] }; // 复合
  29. };

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

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

例子:

  1. // 复合要求
  2. template<typename T>
  3. concept C2 = requires(T x)
  4. {
  5. // 表达式 *x 必须合法
  6. // 并且类型 T::inner 必须合法
  7. // 并且 *x 的结果必须可以转换为 T::inner
  8. {*x} -> std::convertible_to<typename T::inner>;
  9. // 表达式 x + 1 必须合法
  10. // 并且 std::Same<decltype((x + 1)), int> 必须被满足
  11. // 也就是说,(x + 1) 必须是 int 类型的纯右值
  12. {x + 1} -> std::same_as<int>;
  13. // 表达式 x * 1 必须合法
  14. // 并且它的结果必须可以转换到 T
  15. {x * 1} -> std::convertible_to<T>;
  16. };

约束的影响

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/208356
推荐阅读
相关标签
  

闽ICP备14008679号