约束与概念 (自 C++20 起)
类模板、函数模板(包括泛型 lambda 表达式)以及其他模板化函数(通常是类模板的成员)可能与一个约束相关联,该约束指定了对模板参数的要求,可用于选择最合适的函数重载和模板特化。
此类要求的命名集合称为概念。每个概念都是一个在编译时评估的谓词,并成为它用作约束的模板接口的一部分。
#include <cstddef> #include <concepts> #include <functional> #include <string> // Declaration of the concept “Hashable”, which is satisfied by any type “T” // such that for values “a” of type “T”, the expression std::hash<T>{}(a) // compiles and its result is convertible to std::size_t template<typename T> concept Hashable = requires(T a) { { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>; }; struct meow {}; // Constrained C++20 function template: template<Hashable T> void f(T) {} // // Alternative ways to apply the same constraint: // template<typename T> // requires Hashable<T> // void f(T) {} // // template<typename T> // void f(T) requires Hashable<T> {} // // void f(Hashable auto /* parameter-name */) {} int main() { using std::operator""s; f("abc"s); // OK, std::string satisfies Hashable // f(meow{}); // Error: meow does not satisfy Hashable }
对约束的违反会在编译时,即模板实例化过程的早期被检测到,这会产生易于理解的错误消息。
std::list<int> l = {3, -1, 10}; std::sort(l.begin(), l.end()); // Typical compiler diagnostic without concepts: // invalid operands to binary expression ('std::_List_iterator<int>' and // 'std::_List_iterator<int>') // std::__lg(__last - __first) * 2); // ~~~~~~ ^ ~~~~~~~ // ... 50 lines of output ... // // Typical compiler diagnostic with concepts: // error: cannot call std::sort with std::_List_iterator<int> // note: concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied
概念的目的是建模语义类别(Number、Range、RegularFunction),而不是语法限制(HasPlus、Array)。根据 ISO C++ 核心准则 T.20,“指定有意义语义的能力是真正概念的定义性特征,而不是语法约束。”
目录 |
[编辑] 概念
概念是一组命名的要求。概念的定义必须出现在命名空间作用域内。
概念的定义形式如下:
template < template-parameter-list >
|
|||||||||
属性 | - | 任意数量的属性序列 |
// concept template<class T, class U> concept Derived = std::is_base_of<U, T>::value;
概念不能递归地引用自身,也不能被约束。
template<typename T> concept V = V<T*>; // error: recursive concept template<class T> concept C1 = true; template<C1 T> concept Error1 = true; // Error: C1 T attempts to constrain a concept definition template<class T> requires C1<T> concept Error2 = true; // Error: the requires clause attempts to constrain a concept
不允许概念的显式实例化、显式特化或部分特化(原始约束定义的含义不能被改变)。
概念可以在 id-expression 中命名。如果约束表达式被满足,id-expression 的值为 true,否则为 false。
概念也可以在类型约束中命名,作为以下的一部分:
在类型约束中,概念的模板参数比其参数列表要求的少一个,因为上下文中推导的类型被隐式地用作概念的第一个参数。
template<class T, class U> concept Derived = std::is_base_of<U, T>::value; template<Derived<Base> T> void f(T); // T is constrained by Derived<T, Base>
[编辑] 约束
约束是逻辑操作和操作数的序列,它指定了对模板参数的要求。它们可以出现在requires 表达式中,或直接作为概念的主体。
约束有三种(直到 C++26)四种(自 C++26 起)类型:
4) 折叠展开的约束
|
(C++26 起) |
与声明关联的约束通过规范化逻辑 AND 表达式来确定,其操作数按以下顺序排列:
- 为每个约束的类型模板参数或用约束占位符类型声明的非类型模板参数引入的约束表达式,按出现顺序;
- 模板参数列表后面的requires 子句中的约束表达式;
- 在缩写函数模板声明中,为每个具有受限占位符类型的参数引入的约束表达式;
- 尾随requires 子句中的约束表达式。
这个顺序决定了在检查满足性时实例化约束的顺序。
[编辑] 重声明
受约束的声明只能使用相同的语法形式重新声明。不需要诊断。
// These first two declarations of f are fine template<Incrementable T> void f(T) requires Decrementable<T>; template<Incrementable T> void f(T) requires Decrementable<T>; // OK, redeclaration // Inclusion of this third, logically-equivalent-but-syntactically-different // declaration of f is ill-formed, no diagnostic required template<typename T> requires Incrementable<T> && Decrementable<T> void f(T); // The following two declarations have different constraints: // the first declaration has Incrementable<T> && Decrementable<T> // the second declaration has Decrementable<T> && Incrementable<T> // Even though they are logically equivalent. template<Incrementable T> void g(T) requires Decrementable<T>; template<Decrementable T> void g(T) requires Incrementable<T>; // ill-formed, no diagnostic required
[编辑] 合取
两个约束的合取通过在约束表达式中使用 &&
运算符形成。
template<class T> concept Integral = std::is_integral<T>::value; template<class T> concept SignedIntegral = Integral<T> && std::is_signed<T>::value; template<class T> concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
两个约束的合取仅当两个约束都满足时才满足。合取从左到右评估并短路(如果左约束不满足,则不尝试将模板参数替换到右约束中:这防止了由于即时上下文之外的替换导致的失败)。
template<typename T> constexpr bool get_value() { return T::value; } template<typename T> requires (sizeof(T) > 1 && get_value<T>()) void f(T); // #1 void f(int); // #2 void g() { f('A'); // OK, calls #2. When checking the constraints of #1, // 'sizeof(char) > 1' is not satisfied, so get_value<T>() is not checked }
[编辑] 析取
两个约束的析取通过在约束表达式中使用 ||
运算符形成。
两个约束的析取如果其中任一约束满足则满足。析取从左到右评估并短路(如果左约束满足,则不尝试将模板参数替换到右约束中)。
template<class T = void> requires EqualityComparable<T> || Same<T, void> struct equal_to;
[编辑] 原子约束
原子约束由表达式 E 以及从出现在 E 中的模板参数到涉及受约束实体模板参数的模板参数的映射(称为参数映射)组成。
原子约束在约束规范化期间形成。E 从不是逻辑 AND 或逻辑 OR 表达式(它们分别形成合取和析取)。
原子约束的满足性通过将参数映射和模板参数替换到表达式 E 中来检查。如果替换导致无效类型或表达式,则约束不满足。否则,E 在任何左值到右值转换后,必须是类型为 bool 的纯右值常量表达式,并且当且仅当其评估结果为 true 时,约束才满足。
替换后 E 的类型必须严格是 bool。不允许进行类型转换。
template<typename T> struct S { constexpr operator bool() const { return true; } }; template<typename T> requires (S<T>{}) void f(T); // #1 void f(int); // #2 void g() { f(0); // error: S<int>{} does not have type bool when checking #1, // even though #2 is a better match }
如果两个原子约束在源代码级别由相同的表达式形成,并且它们的参数映射等效,则认为它们是相同的。
template<class T> constexpr bool is_meowable = true; template<class T> constexpr bool is_cat = true; template<class T> concept Meowable = is_meowable<T>; template<class T> concept BadMeowableCat = is_meowable<T> && is_cat<T>; template<class T> concept GoodMeowableCat = Meowable<T> && is_cat<T>; template<Meowable T> void f1(T); // #1 template<BadMeowableCat T> void f1(T); // #2 template<Meowable T> void f2(T); // #3 template<GoodMeowableCat T> void f2(T); // #4 void g() { f1(0); // error, ambiguous: // the is_meowable<T> in Meowable and BadMeowableCat forms distinct atomic // constraints that are not identical (and so do not subsume each other) f2(0); // OK, calls #4, more constrained than #3 // GoodMeowableCat got its is_meowable<T> from Meowable }
折叠展开的约束一个折叠展开的约束由约束 令 N 为包展开参数中的元素数量。
template <class T> concept A = std::is_move_constructible_v<T>; template <class T> concept B = std::is_copy_constructible_v<T>; template <class T> concept C = A<T> && B<T>; // in C++23, these two overloads of g() have distinct atomic constraints // that are not identical and so do not subsume each other: calls to g() are ambiguous // in C++26, the folds are expanded and constraint on overload #2 (both move and copy // required), subsumes constraint on overload #1 (just the move is required) template <class... T> requires (A<T> && ...) void g(T...); // #1 template <class... T> requires (C<T> && ...) void g(T...); // #2
|
(C++26 起) |
[编辑] 约束规范化
约束规范化是将约束表达式转换为原子约束的合取和析取序列的过程。表达式的范式定义如下:
- 表达式 (E) 的范式是 E 的范式。
- 表达式 E1 && E2 的范式是 E1 和 E2 的范式的合取。
- 表达式 E1 || E2 的范式是 E1 和 E2 的范式的析取。
- 表达式 C<A1, A2, ... , AN> 的范式,其中
C
命名一个概念,是C
的约束表达式的范式,替换C
的每个原子约束的参数映射中C
的相应模板参数为A1
,A2
, ...,AN
。如果这样的替换到参数映射中导致无效类型或表达式,则程序格式错误,不需要诊断。
template<typename T> concept A = T::value || true; template<typename U> concept B = A<U*>; // OK: normalized to the disjunction of // - T::value (with mapping T -> U*) and // - true (with an empty mapping). // No invalid type in mapping even though // T::value is ill-formed for all pointer types template<typename V> concept C = B<V&>; // Normalizes to the disjunction of // - T::value (with mapping T-> V&*) and // - true (with an empty mapping). // Invalid type V&* formed in mapping => ill-formed NDR
|
(C++26 起) |
- 任何其他表达式 E 的范式是其表达式为 E 且参数映射为恒等映射的原子约束。这包括所有折叠表达式,即使是那些折叠
&&
或||
运算符的表达式。
用户定义的 &&
或 ||
重载对约束规范化没有影响。
[编辑] requires 子句
关键词 requires 用于引入一个requires 子句,它指定了对模板参数或函数声明的约束。
template<typename T> void f(T&&) requires Eq<T>; // can appear as the last element of a function declarator template<typename T> requires Addable<T> // or right after a template parameter list T add(T a, T b) { return a + b; }
在这种情况下,关键字 requires 必须后跟某个常量表达式(因此可以写 requires true),但其意图是使用命名概念(如上述示例所示)或命名概念的合取/析取,或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>(); // error, is_purrable<T>() is not a primary expression template<class T> void h(T) requires (is_purrable<T>()); // OK
[编辑] 约束的偏序
在进行任何进一步分析之前,约束会通过替换每个命名概念和每个requires 表达式的主体进行规范化,直到剩下的是原子约束的合取和析取序列。
如果可以证明 P
蕴含 Q
,直到 P 和 Q 中原子约束的同一性,则称约束 P
包含约束 Q
。(类型和表达式不进行等价分析:N > 0
不包含 N >= 0
)。
具体来说,首先将 P
转换为析取范式,将 Q
转换为合取范式。当且仅当满足以下条件时,P
包含 Q
:
P
的析取范式中的每个析取子句都包含Q
的合取范式中的每个合取子句,其中:- 当且仅当析取子句中有一个原子约束
U
和合取子句中有一个原子约束V
,且U
包含V
时,析取子句包含合取子句; - 当且仅当原子约束
A
和原子约束B
使用上述规则是相同的,原子约束A
包含原子约束B
。
|
(C++26 起) |
包含关系定义了约束的偏序,用于确定:
本节不完整 原因:从上述内容到此处的反向链接。 |
如果声明 D1
和 D2
受约束,并且 D1
相关的约束包含 D2
相关的约束(或 D2
未受约束),则称 D1
至少与 D2
一样受约束。如果 D1
至少与 D2
一样受约束,而 D2
不至少与 D1
一样受约束,则 D1
比 D2
更受约束。
如果满足所有以下条件,则非模板函数 F1
比非模板函数 F2
更受偏序约束:
- 它们具有相同的参数类型列表,省略显式对象参数的类型(自 C++23 起)。
- 如果它们是成员函数,则两者都是同一类的直接成员。
- 如果两者都是非静态成员函数,则它们的对象参数类型相同。
-
F1
比F2
更受约束。
template<typename T> concept Decrementable = requires(T t) { --t; }; template<typename T> concept RevIterator = Decrementable<T> && requires(T t) { *t; }; // RevIterator subsumes Decrementable, but not the other way around template<Decrementable T> void f(T); // #1 template<RevIterator T> void f(T); // #2, more constrained than #1 f(0); // int only satisfies Decrementable, selects #1 f((int*)0); // int* satisfies both constraints, selects #2 as more constrained template<class T> void g(T); // #3 (unconstrained) template<Decrementable T> void g(T); // #4 g(true); // bool does not satisfy Decrementable, selects #3 g(0); // int satisfies Decrementable, selects #4 because it is more constrained template<typename T> concept RevIterator2 = requires(T t) { --t; *t; }; template<Decrementable T> void h(T); // #5 template<RevIterator2 T> void h(T); // #6 h((int*)0); // ambiguous
[编辑] 注意
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__cpp_concepts |
201907L |
(C++20) | 约束 |
202002L |
(C++20) | 条件性平凡的特殊成员函数 |
[编辑] 关键词
[编辑] 缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 2428 | C++20 | 无法将属性应用于概念 | 允许 |
[编辑] 另见
Requires 表达式(C++20) | 生成类型为 bool 的纯右值表达式,描述约束 |