命名空间
变体
操作

约束与概念 (C++20 起)

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围 for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (C++17* 前已弃用)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (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 < 模板形参列表 >

concept 概念名 attr (可选) = 约束表达式;

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 起) 类型:

1) 合取
2) 析取
3) 原子约束
4) 折叠展开约束
(C++26 起)

与声明关联的约束由 正规化逻辑 AND 表达式确定,其操作数按以下顺序排列:

  1. 为每个受约束的类型模板形参或使用受约束的占位符类型声明的非类型模板形参引入的约束表达式,按出现顺序排列;
  2. 模板形参列表之后的 requires 子句中的约束表达式;
  3. 缩略函数模板声明中,为每个带有受约束的占位符类型的形参引入的约束表达式;
  4. 尾随的 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
}

折叠展开约束

折叠展开约束由约束 C 和折叠运算符(&&|| 之一)形成。折叠展开约束是包展开

N 为包展开形参中的元素数

  • 如果包展开无效(例如展开不同大小的包),则折叠展开约束不满足。
  • 如果 N0,则如果折叠运算符为 &&,则折叠展开约束得到满足;如果折叠运算符为 ||,则不满足。
  • 对于正 N 的折叠展开约束,对于 [1N] 中的每个 i,每个包展开形参都替换为对应的第 i 个元素,顺序递增
  • 对于折叠运算符为 && 的折叠展开约束,如果第 j 个元素的替换违反了 C,则折叠展开约束不满足。在这种情况下,对于任何大于 ji,都不会发生替换。否则,折叠展开约束得到满足。
  • 对于折叠运算符为 || 的折叠展开约束,如果第 j 个元素的替换满足了 C,则折叠展开约束得到满足。在这种情况下,对于任何大于 ji,都不会发生替换。否则,折叠展开约束不满足。


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 的正规形式是 E1E2 的正规形式的合取。
  • 表达式 E1 || E2 的正规形式是 E1E2 的正规形式的析取。
  • 表达式 C<A1, A2, ... , AN> 的正规形式,其中 C 命名一个概念,是 C 的约束表达式的正规形式,在将 A1A2、...、AN 替换为 C 的各个模板形参之后,替换到 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
  • 表达式 (E && ...)(... && E) 的正规形式是折叠展开约束,其中 CE 的正规形式,折叠运算符为 &&
  • 表达式 (E || ...)(... || E) 的正规形式是折叠展开约束,其中 CE 的正规形式,折叠运算符为 ||
  • 表达式 (E1 && ... && E2)(E1 || ... || E2) 的正规形式分别是
  • (E1 && ...) && E2(E1 || ...) || E2 分别是,如果 E1 包含未展开的包,或者
  • E1 && (... && E2)E1 || (... || E2) 分别是,否则。
(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 表达式

表达式必须具有以下形式之一:

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
  • 如果折叠展开约束 A 和另一个折叠展开约束 B 具有相同的折叠运算符,A 的约束 C 蕴含 B 的约束 C,并且两个 C 都包含等效的未展开包,则折叠展开约束 A 蕴含折叠展开约束 B
(C++26 起)

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

如果声明 D1D2 是受约束的,并且 D1 的关联约束蕴含 D2 的关联约束(或者如果 D2 是不受约束的),则称 D1至少与 D2 一样受约束。如果 D1 至少与 D2 一样受约束,并且 D2 不至少与 D1 一样受约束,则 D1D2 更受约束

如果所有下列条件均满足,则非模板函数 F1 比非模板函数 F2 *更偏序约束*

  • 它们拥有相同的形参类型列表,省略了显式对象形参的类型(自 C++23 起)
  • 如果它们是成员函数,则两者均为同一类的直接成员。
  • 如果两者均为非静态成员函数,则它们的隐式对象形参拥有相同的类型。
  • F1F2 约束更多。
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) 有条件地平凡的特殊成员函数

[编辑] 关键词

concept, requires, typename

[编辑] 缺陷报告

下列行为变更缺陷报告被追溯应用到先前发布的 C++ 标准。

DR 应用于 已发布行为 正确行为
CWG 2428 C++20 不能将属性应用于概念 允许

[编辑] 参见

Requires 表达式(C++20) 产生类型为 bool 的纯右值表达式,描述约束[编辑]