约束和概念 (自 C++20 起)
类模板 , 函数模板 和非模板函数(通常是类模板的成员)可能与一个 *约束* 相关联,该约束指定对模板参数的要求,这些要求可用于选择最合适的函数重载和模板特化。
此类 要求 的命名集合称为 *概念*。每个概念都是一个谓词,在编译时进行评估,并成为模板接口的一部分,在该模板中它用作约束
#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
概念的目的是对语义类别(数字、范围、正则函数)进行建模,而不是对语法限制(具有加号、数组)进行建模。根据 ISO C++ 核心指南 T.20,“指定有意义语义的能力是真正概念的定义特征,与语法约束相对立。”
内容 |
[编辑] 概念
概念是一组命名的 要求 。概念的定义必须出现在命名空间范围内。
概念的定义具有以下形式
template < 模板参数列表 >
|
|||||||||
attr | - | 任意数量的 属性 的序列 |
// 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 表达式中命名。如果约束表达式满足,则 id 表达式的值为 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 为包扩展参数中的元素数量
|
(自 C++26) |
[编辑] 约束规范化
约束规范化 是将约束表达式转换为一系列原子约束的合取和析取的过程。表达式 *规范形式* 的定义如下
- 表达式 (E) 的规范形式是 E 的规范形式。
- 表达式 E1 && E2 的范式是 E1 和 E2 的范式的合取。
- 表达式 E1 || E2 的范式是 E1 和 E2 的范式的析取。
- 表达式 C<A1, A2, ... , AN> 的范式,其中
C
代表一个概念,是C
的约束表达式的范式,在将A1
、A2
、...、AN
分别替换为C
在每个原子约束的参数映射中的模板参数之后得到。如果任何这样的替换导致参数映射中出现无效类型或表达式,则程序格式错误,不需要诊断。
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 且参数映射为恒等映射的原子约束。这包括所有 折叠表达式,即使是那些对
&&
或||
操作符进行折叠的表达式。
&&
或 ||
的用户定义重载对约束规范化没有影响。
[edit] 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
[edit] 约束的部分排序
在进行任何进一步分析之前,约束会通过 规范化,将每个命名概念和每个 requires 表达式 的主体进行替换,直到剩余的只是对原子约束的合取和析取序列。
如果可以证明约束 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
更受约束。
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
[edit] 注释
功能测试宏 | 值 | Std | 功能 |
---|---|---|---|
__cpp_concepts |
201907L | (C++20) | 约束 |
202002L | (C++20) | 有条件地平凡的 特殊成员函数 |
[edit] 关键字
[edit] 缺陷报告
以下改变行为的缺陷报告被追溯应用于以前发布的 C++ 标准。
DR | 应用于 | 已发布的行为 | 正确行为 |
---|---|---|---|
CWG 2428 | C++20 | 无法对概念应用属性 | 允许 |
[edit] 参见
Requires 表达式(C++20) | 产生一个类型为 bool 的 prvalue 表达式,描述了约束 |