约束与概念
此页面描述了一个实验性的核心语言特性。有关标准库规范中使用的具名类型要求,请参阅具名要求
类模板,函数模板和非模板函数(通常是类模板的成员)可能与约束关联,约束指定了对模板实参的要求,可用于选择最合适的函数重载和模板特化。
约束也可以用于限制变量声明和函数返回类型中的自动类型推导,使其仅限于满足指定要求的类型。
此类要求的具名集合称为概念。每个概念都是一个谓词,在编译时求值,并成为模板接口的一部分,在模板接口中它被用作约束
#include <string> #include <locale> using namespace std::literals; // Declaration of the concept "EqualityComparable", which is satisfied by // any type T such that for values a and b of type T, // the expression a==b compiles and its result is convertible to bool template<typename T> concept bool EqualityComparable = requires(T a, T b) { { a == b } -> bool; }; void f(EqualityComparable&&); // declaration of a constrained function template // template<typename T> // void f(T&&) requires EqualityComparable<T>; // long form of the same int main() { f("abc"s); // OK, std::string is EqualityComparable f(std::use_facet<std::ctype<char>>(std::locale{})); // Error: not EqualityComparable }
约束冲突在编译时,模板实例化过程的早期被检测到,这会产生易于理解的错误消息。
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,“指定有意义的语义的能力是真正概念的定义特征,而不是语法约束。”
如果支持特性测试,则此处描述的特性由宏常量 __cpp_concepts 指示,其值等于或大于 201507。
目录 |
[编辑] 占位符
无约束占位符 auto 和约束占位符,其形式为 concept-name <
template-argument-list(可选)>
,是待推导类型的占位符。
占位符可以出现在变量声明中(在这种情况下,它们从初始化器中推导出来)或函数返回类型中(在这种情况下,它们从返回语句中推导出来)
std::pair<auto, auto> p2 = std::make_pair(0, 'a'); // first auto is int, // second auto is char Sortable x = f(y); // the type of x is deduced from the return type of f, // only compiles if the type satisfies the constraint Sortable auto f(Container) -> Sortable; // return type is deduced from the return statement // only compiles if the type satisfies Sortable
占位符也可以出现在参数中,在这种情况下,它们将函数声明转换为模板声明(如果占位符是约束的,则为约束模板声明)
void f(std::pair<auto, EqualityComparable>); // this is a template with two parameters: // unconstrained type parameter and a constrained non-type parameter
约束占位符可以用于任何可以使用 auto 的地方,例如,在泛型 lambda 声明中
auto gl = [](Assignable& a, auto* b) { a = *b; };
如果约束类型说明符指定了非类型或模板,但被用作约束占位符,则程序是非良构的
template<size_t N> concept bool Even = (N%2 == 0); struct S1 { int n; }; int Even::* p2 = &S1::n; // error, invalid use of a non-type concept void f(std::array<auto, Even>); // error, invalid use of a non-type concept template<Even N> void f(std::array<auto, N>); // OK
[编辑] 缩写模板
如果一个或多个占位符出现在函数参数列表中,则该函数声明实际上是函数模板声明,其模板参数列表包括每个唯一占位符的虚构参数,按出现顺序排列
// short form void g1(const EqualityComparable*, Incrementable&); // long form: // template<EqualityComparable T, Incrementable U> void g1(const T*, U&); // longer form: // template<typename T, typename U> // void g1(const T*, U&) requires EqualityComparable<T> && Incrementable<U>; void f2(std::vector<auto*>...); // long form: template<typename... T> void f2(std::vector<T*>...); void f4(auto (auto::*)(auto)); // long form: template<typename T, typename U, typename V> void f4(T (U::*)(V));
等效约束类型说明符引入的所有占位符都具有相同的虚构模板参数。但是,每个无约束说明符 (auto
) 总是引入不同的模板参数
void f0(Comparable a, Comparable* b); // long form: template<Comparable T> void f0(T a, T* b); void f1(auto a, auto* b); // long form: template<typename T, typename U> f1(T a, U* b);
函数模板和类模板都可以使用模板引入来声明,其语法为 concept-name {
parameter-list(可选)}
,在这种情况下,不需要关键字 template
:模板引入的 parameter-list 中的每个参数都成为模板参数,其种类(类型,非类型,模板)由命名概念中相应参数的种类确定。
除了声明模板外,模板引入还关联一个谓词约束(见下文),该约束命名(对于变量概念)或调用(对于函数概念)引入所命名的概念。
EqualityComparable{T} class Foo; // long form: template<EqualityComparable T> class Foo; // longer form: template<typename T> requires EqualityComparable<T> class Foo; template<typename T, int N, typename... Xs> concept bool Example = ...; Example{A, B, ...C} struct S1; // long form template<class A, int B, class... C> requires Example<A,B,C...> struct S1;
对于函数模板,模板引入可以与占位符结合使用
Sortable{T} void f(T, auto); // long form: template<Sortable T, typename U> void f(T, U); // alternative using only placeholders: void f(Sortable, auto);
本节不完整 原因:润色模板声明页面以链接到此处 |
[编辑] 概念
概念是具名要求集合。概念的定义出现在命名空间作用域中,形式为函数模板定义(在这种情况下,它被称为函数概念)或变量模板定义(在这种情况下,它被称为变量概念)。唯一的区别是关键字 concept 出现在 decl-specifier-seq 中
// variable concept from the standard library (Ranges TS) template <class T, class U> concept bool Derived = std::is_base_of<U, T>::value; // function concept from the standard library (Ranges TS) template <class T> concept bool EqualityComparable() { return requires(T a, T b) { {a == b} -> Boolean; {a != b} -> Boolean; }; }
以下限制适用于函数概念
- 不允许使用
inline
和constexpr
,该函数自动为inline
和constexpr
- 不允许使用
friend
和virtual
- 不允许使用异常规范,该函数自动为
noexcept(true)
。 - 不能稍后声明和定义,不能重新声明
- 返回类型必须为
bool
- 不允许返回类型推导
- 参数列表必须为空
- 函数体必须仅包含一个
return
语句,其参数必须是约束表达式(谓词约束,其他约束的合取/析取,或 requires 表达式,见下文)
以下限制适用于变量概念
- 必须具有类型
bool
- 不能在没有初始化器的情况下声明
- 不能在类作用域中声明或位于类作用域。
- 不允许使用
constexpr
,该变量自动为constexpr
- 初始化器必须是约束表达式(谓词约束,约束的合取/析取,或 requires 表达式,见下文)
概念不能在函数体或变量的初始化器中递归引用自身
template<typename T> concept bool F() { return F<typename T::type>(); } // error template<typename T> concept bool V = V<T*>; // error
不允许概念的显式实例化,显式特化或部分特化(不能更改约束原始定义的含义)
[编辑] 约束
约束是逻辑操作序列,用于指定对模板实参的要求。它们可以出现在requires 表达式中(见下文),并直接作为概念的主体
有 9 种类型的约束
前三种类型的约束可以直接作为概念的主体或作为临时的 requires 子句出现
template<typename T> requires // requires-clause (ad-hoc constraint) sizeof(T) > 1 && get_value<T>() // conjunction of two predicate constraints void f(T);
当多个约束附加到同一声明时,总约束是按以下顺序排列的合取:由模板引入引入的约束,每个模板参数的约束(按出现顺序),模板参数列表后的 requires 子句,每个函数参数的约束(按出现顺序),尾随 requires 子句
// the declarations declare the same constrained function template // with the constraint Incrementable<T> && Decrementable<T> template<Incrementable T> void f(T) requires Decrementable<T>; template<typename T> requires Incrementable<T> && Decrementable<T> void f(T); // ok // 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. // The second declaration is ill-formed, no diagnostic required template<Incrementable T> requires Decrementable<T> void g(); template<Decrementable T> requires Incrementable<T> void g(); // error
[编辑] 合取
约束 P
和 Q
的合取指定为 P && Q。
// example concepts from the standard library (Ranges TS) template <class T> concept bool Integral = std::is_integral<T>::value; template <class T> concept bool SignedIntegral = Integral<T> && std::is_signed<T>::value; template <class T> concept bool UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
仅当两个约束都满足时,才满足两个约束的合取。合取从左到右求值并短路(如果左侧约束不满足,则不会尝试将模板实参替换为右侧约束:这可以防止由于立即上下文之外的替换而导致的失败)。约束合取中不允许用户定义的 operator&&
重载。
[编辑] 析取
约束 P
和 Q
的析取指定为 P || Q。
如果满足任一约束,则满足两个约束的析取。析取从左到右求值并短路(如果左侧约束满足,则不会尝试将模板实参推导为右侧约束)。约束析取中不允许用户定义的 operator||
重载。
// example constraint from the standard library (Ranges TS) template <class T = void> requires EqualityComparable<T>() || Same<T, void> struct equal_to;
[编辑] 谓词约束
谓词约束是 bool 类型的常量表达式。仅当其求值为 true 时才满足
template<typename T> concept bool Size32 = sizeof(T) == 4;
谓词约束可以指定对非类型模板参数和模板模板实参的要求。
谓词约束必须直接求值为 bool,不允许转换
template<typename T> struct S { constexpr explicit operator bool() const { return true; } }; template<typename T> requires S<T>{} // bad predicate constraint: S<T>{} is not bool void f(T); f(0); // error: constraint never satisfied
[编辑] 要求
关键字 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; }
true
,否则为 falsetemplate<typename T> concept bool Addable = requires (T x) { x + x; }; // requires-expression template<typename T> requires Addable<T> // requires-clause, not requires-expression T add(T a, T b) { return a + b; } template<typename T> requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice T add(T a, T b) { return a + b; }
requires 表达式的语法如下
requires ( parameter-list(可选) ) { requirement-seq } |
|||||||||
parameter-list | - | 逗号分隔的参数列表,类似于函数声明中的参数列表,但不允许默认实参,并且最后一个参数不能是省略号。这些参数没有存储,链接或生命周期。这些参数的作用域一直持续到 requirement-seq 的右花括号 } 。如果未使用任何参数,则可以省略圆括号 |
requirement-seq | - | 空白分隔的要求序列,如下所述(每个要求以分号结尾)。每个要求都为此 requires 表达式定义的约束合取添加另一个约束。 |
requirements-seq 中的每个要求都是以下之一
- 简单要求
- 类型要求
- 复合要求
- 嵌套要求
要求可以引用作用域内的模板参数和 parameter-list 中引入的局部参数。当参数化时,requires 表达式被称为引入参数化约束
将模板实参替换为 requires 表达式可能会导致在其要求中形成无效的类型或表达式。在这种情况下,
- 如果替换失败发生在 模板实体声明之外使用的 requires 表达式中,则程序是非良构的。
- 如果 requires 表达式用于 模板实体的声明中,则相应的约束被视为“不满足”,并且替换失败不是错误,但是
- 如果对于每个可能的模板实参,在 requires 表达式中都会发生替换失败,则程序是非良构的,无需诊断
template<class T> concept bool C = requires { new int[-(int)sizeof(T)]; // invalid for every T: ill-formed, no diagnostic required };
[编辑] 简单要求
简单要求是任意表达式语句。要求是表达式有效(这是一个表达式约束)。与谓词约束不同,不进行求值,仅检查语言正确性。
template<typename T> concept bool Addable = requires (T a, T b) { a + b; // "the expression a+b is a valid expression that will compile" }; // example constraint from the standard library (ranges TS) template <class T, class U = T> concept bool Swappable = requires(T&& t, U&& u) { swap(std::forward<T>(t), std::forward<U>(u)); swap(std::forward<U>(u), std::forward<T>(t)); };
[编辑] 类型要求
类型要求是关键字 typename 后跟类型名称,可以选择性地限定。要求是命名的类型存在(类型约束):这可用于验证某个命名的嵌套类型是否存在,或者类模板特化是否命名类型,或者别名模板是否命名类型。
template<typename T> using Ref = T&; template<typename T> concept bool C = requires { typename T::inner; // required nested member name typename S<T>; // required class template specialization typename Ref<T>; // required alias template substitution }; //Example concept from the standard library (Ranges TS) template <class T, class U> using CommonType = std::common_type_t<T, U>; template <class T, class U> concept bool Common = requires (T t, U u) { typename CommonType<T, U>; // CommonType<T, U> is valid and names a type { CommonType<T, U>{std::forward<T>(t)} }; { CommonType<T, U>{std::forward<U>(u)} }; };
[编辑] 复合要求
复合要求的形式为
{ expression } noexcept (可选) trailing-return-type(可选) ; |
|||||||||
并指定以下约束的合取
noexcept
,则表达式也必须是 noexcept 的(异常约束)template<typename T> concept bool C2 = requires(T x) { {*x} -> typename T::inner; // the expression *x must be valid // AND the type T::inner must be valid // AND the result of *x must be convertible to T::inner }; // Example concept from the standard library (Ranges TS) template <class T, class U> concept bool Same = std::is_same<T,U>::value; template <class B> concept bool Boolean = requires(B b1, B b2) { { bool(b1) }; // direct initialization constraint has to use expression { !b1 } -> bool; // compound constraint requires Same<decltype(b1 && b2), bool>; // nested constraint, see below requires Same<decltype(b1 || b2), bool>; };
[编辑] 嵌套要求
嵌套要求是另一个 requires 子句,以分号结尾。这用于引入谓词约束(见上文),这些约束根据应用于局部参数的其他具名概念来表示(在 requires 子句之外,谓词约束不能使用参数,并且将表达式直接放在 requires 子句中会使其成为表达式约束,这意味着它不被求值)
// example constraint from Ranges TS template <class T> concept bool Semiregular = DefaultConstructible<T> && CopyConstructible<T> && Destructible<T> && CopyAssignable<T> && requires(T a, size_t n) { requires Same<T*, decltype(&a)>; // nested: "Same<...> evaluates to true" { a.~T() } noexcept; // compound: "a.~T()" is a valid expression that doesn't throw requires Same<T*, decltype(new T)>; // nested: "Same<...> evaluates to true" requires Same<T*, decltype(new T[n])>; // nested { delete new T }; // compound { delete new T[n] }; // compound };
[编辑] 概念解析
像任何其他函数模板一样,函数概念(但不是变量概念)可以重载:可以提供多个概念定义,它们都使用相同的 concept-name。
当 concept-name(可能是限定名)出现在以下位置时,执行概念解析
template<typename T> concept bool C() { return true; } // #1 template<typename T, typename U> concept bool C() { return true; } // #2 void f(C); // the set of concepts referred to by C includes both #1 and #2; // concept resolution (see below) selects #1.
为了执行概念解析,每个匹配名称(和限定符,如果有)的概念的模板参数与概念实参序列匹配,概念实参是模板实参和通配符。通配符可以匹配任何种类的模板参数(类型,非类型,模板)。实参集以不同的方式构造,具体取决于上下文
template<typename T> concept bool C1() { return true; } // #1 template<typename T, typename U> concept bool C1() { return true; } // #2 void f1(const C1*); // <wildcard> matches <T>, selects #1
template<typename T> concept bool C1() { return true; } // #1 template<typename T, typename U> concept bool C1() { return true; } // #2 void f2(C1<char>); // <wildcard, char> matches <T, U>, selects #2
template<typename... Ts> concept bool C3 = true; C3{T} void q2(); // OK: <T> matches <...Ts> C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
template<typename T> concept bool C() { return true; } // #1 template<typename T, typename U> concept bool C() { return true; } // #2 template <typename T> void f(T) requires C<T>(); // matches #1
概念解析通过将每个实参与每个可见概念的相应参数匹配来执行。默认模板实参(如果使用)会为每个不对应于实参的参数实例化,然后附加到实参列表。模板参数仅当其具有相同的种类(类型,非类型,模板)时才匹配实参,除非实参是通配符。参数包匹配零个或多个实参,只要所有实参都匹配种类中的模式(除非它们是通配符)。
如果任何实参与其对应的参数不匹配,或者如果实参多于参数且最后一个参数不是包,则概念不可行。如果可行概念为零个或多于一个,则程序是非良构的。
template<typename T> concept bool C2() { return true; } template<int T> concept bool C2() { return true; } template<C2<0> T> struct S1; // error: <wildcard, 0> matches // neither <typename T> nor <int T> template<C2 T> struct S2; // both #1 and #2 match: error
本节不完整 原因:需要一个包含有意义概念的示例,而不是这些“return true”占位符 |
[编辑] 约束的部分排序
在任何进一步分析之前,通过替换每个名称概念和每个 requires 表达式的主体来规范化约束,直到剩下的是原子约束的合取和析取序列,原子约束是谓词约束,表达式约束,类型约束,隐式转换约束,实参推导约束和异常约束。
如果可以证明 P
蕴含 Q
而无需分析类型和表达式的等效性(因此 N >= 0
不蕴含 N > 0
),则概念 P
被称为蕴含概念 Q
具体而言,首先将 P
转换为析取范式,将 Q
转换为合取范式,然后按如下方式比较它们
- 每个原子约束
A
蕴含等效的原子约束A
- 每个原子约束
A
蕴含析取A||B
,但不蕴含合取A&&B
- 每个合取
A&&B
蕴含A
,但析取A||B
不蕴含A
蕴含关系定义了约束的部分顺序,用于确定
- 重载解析中非模板函数的最佳可行候选者
- 重载集中非模板函数的地址
- 模板模板实参的最佳匹配
- 类模板特化的部分排序
- 函数模板的部分排序
本节不完整 原因:从上面到这里的反向链接 |
如果声明 D1
和 D2
是约束的,并且 D1 的规范化约束蕴含 D2 的规范化约束(或者如果 D1 是约束的而 D2 是无约束的),则称 D1 至少与 D2 一样受约束。如果 D1 至少与 D2 一样受约束,并且 D2 不至少与 D1 一样受约束,则 D1 比 D2 更受约束。
template<typename T> concept bool Decrementable = requires(T t) { --t; }; template<typename T> concept bool RevIterator = Decrementable<T> && requires(T t) { *t; }; // RevIterator subsumes Decrementable, but not the other way around // RevIterator is more constrained as Decrementable void f(Decrementable); // #1 void f(RevIterator); // #2 f(0); // int only satisfies Decrementable, selects #1 f((int*)0); // int* satisfies both constraints, selects #2 as more constrained void g(auto); // #3 (unconstrained) void g(Decrementable); // #4 g(true); // bool does not satisfy Decrementable, selects #3 g(0); // int satisfies Decrementable, selects #4 because it is more constrained
[编辑] 关键字
[编辑] 编译器支持
GCC >= 6.1 支持此技术规范(需要选项 -fconcepts)