命名空间
变体
操作

约束与概念

来自 cppreference.cn


此页面描述了一个实验性的核心语言特性。有关标准库规范中使用的具名类型要求,请参阅具名要求

类模板函数模板和非模板函数(通常是类模板的成员)可能与约束关联,约束指定了对模板实参的要求,可用于选择最合适的函数重载和模板特化。

约束也可以用于限制变量声明和函数返回类型中的自动类型推导,使其仅限于满足指定要求的类型。

此类要求的具名集合称为概念。每个概念都是一个谓词,在编译时求值,并成为模板接口的一部分,在模板接口中它被用作约束

#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; };
}

以下限制适用于函数概念

  • 不允许使用 inlineconstexpr,该函数自动为 inlineconstexpr
  • 不允许使用 friendvirtual
  • 不允许使用异常规范,该函数自动为 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 种类型的约束

1) 合取
2) 析取
3) 谓词约束
4) 表达式约束(仅在requires 表达式中)
5) 类型约束(仅在requires 表达式中)
6) 隐式转换约束(仅在requires 表达式中)
7) 实参推导约束(仅在requires 表达式中)
8) 异常约束(仅在requires 表达式中)
9) 参数化约束(仅在requires 表达式中)

前三种类型的约束可以直接作为概念的主体或作为临时的 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

[编辑] 合取

约束 PQ 的合取指定为 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&& 重载。

[编辑] 析取

约束 PQ 的析取指定为 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 以两种方式使用

1) 引入 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 表达式
2) 开始一个 requires 表达式,它是一个 bool 类型的纯右值表达式,描述对某些模板实参的约束。如果满足相应的概念,则此表达式为 true,否则为 false
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 表达式的语法如下

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(可选) ;

并指定以下约束的合取

1) expression 是有效表达式(表达式约束
2) 如果使用了 noexcept,则表达式也必须是 noexcept 的(异常约束
3) 如果 trailing-return-type 命名了使用占位符的类型,则该类型必须可从表达式的类型推导出来(实参推导约束
4) 如果 trailing-return-type 命名了不使用占位符的类型,则添加另外两个约束
4a)trailing-return-type 命名的类型是有效的(类型约束
4b) 表达式的结果是隐式可转换为该类型的(隐式转换约束
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(可能是限定名)出现在以下位置时,执行概念解析

1) 约束类型说明符 void f(Concept); std::vector<Concept> x = ...;
2) 约束参数 template<Concept T> void f();
3) 模板引入 Concept{T} struct X;
4) 约束表达式 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.

为了执行概念解析,每个匹配名称(和限定符,如果有)的概念的模板参数概念实参序列匹配,概念实参是模板实参和通配符。通配符可以匹配任何种类的模板参数(类型,非类型,模板)。实参集以不同的方式构造,具体取决于上下文

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
2) 对于用作约束类型说明符或参数一部分的概念名称,如果概念名称与模板实参列表一起使用,则实参列表是单个通配符,后跟该实参列表。
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) 如果概念出现在模板引入中,则实参列表是占位符序列,其长度与模板引入中的参数列表相同
template<typename... Ts>
concept bool C3 = true;
C3{T} void q2();     // OK: <T> matches <...Ts>
C3{...Ts} void q1(); // OK: <...Ts> matches <...Ts>
4) 如果概念作为模板 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

概念解析通过将每个实参与每个可见概念的相应参数匹配来执行。默认模板实参(如果使用)会为每个不对应于实参的参数实例化,然后附加到实参列表。模板参数仅当其具有相同的种类(类型,非类型,模板)时才匹配实参,除非实参是通配符。参数包匹配零个或多个实参,只要所有实参都匹配种类中的模式(除非它们是通配符)。

如果任何实参与其对应的参数不匹配,或者如果实参多于参数且最后一个参数不是包,则概念不可行。如果可行概念为零个或多于一个,则程序是非良构的。

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

[编辑] 约束的部分排序

在任何进一步分析之前,通过替换每个名称概念和每个 requires 表达式的主体来规范化约束,直到剩下的是原子约束的合取和析取序列,原子约束是谓词约束,表达式约束,类型约束,隐式转换约束,实参推导约束和异常约束。

如果可以证明 P 蕴含 Q 而无需分析类型和表达式的等效性(因此 N >= 0 不蕴含 N > 0),则概念 P 被称为蕴含概念 Q

具体而言,首先将 P 转换为析取范式,将 Q 转换为合取范式,然后按如下方式比较它们

  • 每个原子约束 A 蕴含等效的原子约束 A
  • 每个原子约束 A 蕴含析取 A||B,但不蕴含合取 A&&B
  • 每个合取 A&&B 蕴含 A,但析取 A||B 不蕴含 A

蕴含关系定义了约束的部分顺序,用于确定

如果声明 D1D2 是约束的,并且 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

[编辑] 关键字

concept, requires

[编辑] 编译器支持

GCC >= 6.1 支持此技术规范(需要选项 -fconcepts