Constraints and concepts
制约因素和概念
本页面描述了一个实验性的核心语言特性。有关标准库规范中使用的命名类型要求,请参见图书馆概念...
类模板,,,函数模板,而非模板函数%28通常是类模板%#number0#的成员与约束
,它指定对模板参数的要求,可用于选择最合适的函数重载和模板专门化。
约束还可用于将变量声明和函数返回类型中的自动类型推断限制为仅满足指定需求的类型。
这类要求的命名集称为概念
.每个概念
都是一个谓词,在编译时计算,并成为模板接口的一部分,其中它被用作约束:
二次
#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
二次
概念的目的是建模语义类别%28 Number、Range、RegularFunction%29,而不是语法限制%28 HasPlus、Array%29。根据ISO C++核心准则T.20“指定有意义语义的能力是一个真正概念的定义特征,而不是语法约束。”
如果支持功能测试,则这里描述的特性由宏常量表示。__cpp_concepts
值等于或大于201507
...
占位符
无约束占位符auto和约束占位符其中有形式概念名称<模板-参数-列表%28可选%29>,是要推导的类型的占位符。
占位符可能出现在变量声明%28中,在这种情况下,它们是从初始化器%29中推导出来的,或者在函数返回类型%28中,它们是从返回语句%29中推导出来的。
二次
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
二次
占位符也可能出现在参数中,在这种情况下,如果占位符被约束为%29,则它们会将函数声明转换为模板声明%28约束。
二次
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)
二次
由等效约束类型说明符引入的所有占位符都具有相同的模板参数。但是,每个无约束说明符%28auto
%29总是引入不同的模板参数。
二次
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
二次
函数模板和类模板都可以使用模板介绍
,它具有语法概念-名称。{
参数-列出%28可选%29},在这种情况下,关键字template
不需要:模板导入参数列表中的每个参数都成为模板参数,其种类%28类型、非类型、模板%29取决于命名概念中相应参数的种类。
除了声明模板之外,模板介绍还将谓词约束
%28参见%29下面的变量概念%28或函数概念%29调用%28--介绍中命名的概念。
二次
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
二次
概念
概念是一组指定的需求。概念的定义出现在命名空间范围,其形式为功能模板定义%28,在这种情况下,它被调用功能概念
%29或可变模板定义%28,在这种情况下,它被调用可变概念
29%。唯一的区别是关键字concept
出现在decl-说明符-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
语句,其参数必须是约束表达式
%28谓词约束、其他约束的连接/分离或要求-表达式,请参见下面的%29。
以下限制适用于可变概念:
- 必须具有以下类型
bool
- 不能在没有初始化项的情况下声明
- 无法声明或在类范围内。
constexpr
不允许,则变量将自动。constexpr
- 初始化器必须是约束表达式%28谓词约束、约束的连接/分离或需求表达式,参见下面的%29。
概念不能递归地在函数的主体或变量的初始化器中引用它们自己:
二次
template<typename T>
concept bool F() { return F<typename T::type>( } // error
template<typename T>
concept bool V = V<T*>; // error
二次
不允许对概念进行显式实例化、显式专门化或部分专门化(%28)。约束的原始定义的含义不能更改为%29。
约束
约束是一个逻辑操作序列,它指定模板参数的需求。它们可以出现在_Required-表达式%28中,见%29下面,并直接作为概念体出现。
有9种限制:
1%29连词
2%29次分离
3%29谓词约束
4%29表达式约束%28只在要求-表达式
%29
5%29类型约束%28只在要求-表达式
%29
6%29隐式转换约束%28只在要求-表达式
%29
7%29参数演绎约束%28仅在要求-表达式
%29
8%29异常约束%28仅在要求-表达式
%29
9%29参数化约束%28只在要求-表达式
%29
四种约束可以直接显示为概念的主体,也可以作为特殊的需求-子句出现:
二次
template<typename T>
requires // requires-clause (ad-hoc constraint)
sizeof(T) > 1 && get_value<T>() // conjunction of two predicate constraints
void f(T
二次
当多个约束附加到同一声明时,总体约束按以下顺序为连接:模板介绍
,每个模板参数的约束按外观顺序排列,要求
子句后的模板参数列表,每个函数参数的约束按外观顺序排列,要求
条款:
二次
// 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>;
二次
只有当两个约束都满足时,才能满足两个约束的联合。连接符从左到右计算,如果不满足左约束,则不尝试将模板参数替换为右约束:这将防止因替换而导致的即时上下文%29以外的失败。用户定义的重载operator&&
在约束连词中不允许。
分离
分离约束P
和Q
指定为P || Q
...
如果满足两个约束,则满足两个约束的分离。从左到右计算析取,如果满足左约束,则短路%28,不尝试将模板参数演绎到右侧约束中。用户定义的重载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
使用的方式有两种:
1%29介绍要求条款
,它指定模板参数或函数声明的约束。
二次
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; }
二次
在这种情况下,关键字要求
必须后面跟着一些常量表达式%28,这样才有可能编写“Required true”%29,但目的是在上面的示例中使用命名概念%28,或者是命名概念的连接/分离,或者是要求-表达式
被使用了。
2%29开始要求-表达式
,它是类型的prvalue表达式。bool
描述某些模板参数的约束。这种表达方式是true
如果满足了相应的概念,而错误的除外:
二次
template<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 ( parameter-list(optional) ) { requirement-seq } | | |
---|
parameter-list | - | a comma-separated list of parameters like in a function declaration, except that default arguments are not allowed and the last parameter cannot be an ellipsis. These parameters have no storage, linkage or lifetime. These parameters are in scope until the closing } of the requirement-seq. If no parameters are used, the round parentheses may be omitted as well |
---|---|---|
requirement-seq | - | whitespace-separated sequence of requirements, described below (each requirement ends with a semicolon). Each requirement adds another constraint to the conjunction of constraints that this requires-expression defines. |
Requirements-seq中的每一项要求都是下列之一:
- 简单要求
- 类型要求
- 复合要求
- 嵌套需求
需求可以引用范围内的模板参数和参数列表中引入的本地参数。当参数化时,要求表达式被称为引入了参数化约束
...
将模板参数替换为需求表达式可能会导致其需求中的无效类型或表达式的形成。在这种情况下,
- 如果替换失败发生在在模板实体声明,则程序格式不正确。
- 如果在模板实体,则相应的约束被视为“不满足”,并且替换失败不是错误。然而,
- 如果每个可能的模板参数的需求表达式中都会出现替换失败,则程序的格式不正确,不需要诊断:
二次
template<class T> concept bool C = requires {
new int[-(int)sizeof(T)]; // invalid for every T: ill-formed, no diagnostic required
};
二次
简单要求
一个简单的要求是一个任意表达式语句。要求表达式为有效%28--这是一个表达式约束
29%。与谓词约束不同,不进行计算,只检查语言的正确性。
二次
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
后面跟着类型名称,可选限定。要求命名类型存在%28a类型约束
%29:这可以用于验证某个命名的嵌套类型是否存在,或者类模板专门化是否指定了类型,或者别名模板是否命名了类型。
二次
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(optional) trailing-return-type(optional) ; | | |
---|
并指定下列约束的连接:
1%29表达式为有效表达式%28表达式约束
%29
2%29noexcept
,表达式也必须为no,%28除外。异常约束
%29
3%29如果后继返回类型指定使用占位符的类型,则类型必须可从表达式%28的类型中还原。参数演绎约束
%29
4%29如果后继返回类型命名为不使用占位符的类型,则会添加两个约束:
4A%29尾随返回类型命名的类型有效%28类型约束
%29
4B%29表达式的结果是隐式可兑换%28隐式转换约束
%29
二次
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>;
};
二次
嵌套需求
嵌套要求是另一个要求。要求条款
,以分号结尾。这是用来介绍谓词约束
%28参见上面%29表示的其他命名概念,这些概念应用于Required子句之外的本地参数%28,谓词约束
可以%27T使用参数,而将表达式直接放在Required子句中使其成为表达式约束,这意味着它没有计算%29。
二次
// 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
};
二次
概念解析
与任何其他函数模板一样,可以重载函数概念%28而不是可变的概念%29:可以提供多个概念定义,它们都使用相同的概念名称。
中出现的概念名称%28(可能是限定%29)时执行概念解析。
1%29 a约束型说明符void f(Conceptstd::vector<Concept> x = ...;
2%29 a约束参数template<Concept T> void f(
3%29模板游戏攻略Concept{T} struct X;
4%29 a约束表达式template<typename T> void f() requires Concept<T>;
二次
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.
二次
为了执行概念解析,模板参数
与名称%28和限定条件匹配的每个概念,如果任何%29与概念论证
,它们是模板参数
和通配符
通配符
可以匹配任意类型的%28类型、非类型、模板%29的模板参数
。根据上下文的不同,参数集的构造是不同的。
1%29对于用作约束类型说明符或参数的一部分的概念名称,如果概念名称没有参数列表,则参数列表是单个通配符。
二次
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
二次
2%29对于作为约束类型说明符或参数的一部分的概念名称,如果概念名称与模板参数列表一起使用,则参数列表是一个通配符,后面跟着该参数列表。
二次
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
二次
3%29如果一个概念出现在模板介绍中,那么参数列表就是一个占位符序列,只要模板介绍中的参数列表就可以了。
二次
template<typename... Ts>
concept bool C3 = true;
C3{T} void q2( // OK: <T> matches <...Ts>
C3{...Ts} void q1( // OK: <...Ts> matches <...Ts>
二次
4%29如果一个概念出现为模板id的名称,则概念参数列表就是该模板id的参数序列。
二次
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
二次
概念解析是通过将每个参数与每个可见概念的相应参数匹配来执行的。默认模板参数%28(如果使用%29)将为每个参数实例化,而每个参数的%27T对应于一个参数,然后追加到参数列表中。只有当参数具有相同的类型%28类型、非类型和模板%29时,模板参数才与参数匹配,除非参数是通配符。一个参数包匹配零个或多个参数,只要所有参数匹配类型为%28,除非它们是通配符%29。
如果任何参数与其相应的参数不匹配,或者有比参数更多的参数,而且最后一个参数不是一个包,则这个概念是不可行的。如果有零个或多个可行的概念,程序是错误的.
二次
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
二次
约束的偏序
在进一步分析之前,约束条件是归一化
通过替换每个名称的主体和每个需要的表达式,直到剩下的是原子约束上的连词和析取序列,即谓词约束、表达式约束、类型约束、隐式转换约束、参数推导约束和异常约束。
概念P据说归入概念Q如果可以证明P暗示Q而不分析等价物%28SO的类型和表达式N >= 0不包括在内N > 029%。
特别是,首先P
被转换为析取范式,并且Q
被转换为合合范式,并按以下方式进行比较:
- 每个原子约束
A
包含等效原子约束A
- 每个原子约束
A
包含连词A&&B
也不包括分离A||B
- 每次分离
A||B
总括A
,但是连词A&&B
不包括在内A
包含关系定义了约束的部分顺序,用于确定:
- 中的非模板函数的最佳可行人选。过载分辨率
- 大非模板函数的地址在过载集中
- 模板模板参数的最佳匹配
- 类模板的偏序化
- 偏序函数模板
IF声明D1
和D2
被约束,d1%27s规范化约束包含d2%27s规范化约束%28,如果d1受约束,d2为无约束%29,则d1被称为至少有约束
如果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
二次
关键词
concept
,,,requires
...
编译器支持
gcc>=6.1支持此技术规范%28所需选项-fconcepts29%。
© cppreference.com
在CreativeCommonsAttribution下授权-ShareAlike未移植许可v3.0。