命名空间
变体
操作

约束和概念 (自 C++20 起)

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
表达式
替代表示
字面量
布尔值 - 整数 - 浮点数
字符 - 字符串 - nullptr (C++11)
用户定义的 (C++11)
实用程序
属性 (C++11)
类型
typedef 声明
类型别名声明 (C++11)
强制转换
内存分配
特定于类的函数属性
explicit (C++11)
static

特殊成员函数
模板
其他
 
 
 
 

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

此类 要求 的命名集合称为 *概念*。每个概念都是一个谓词,在编译时进行评估,并成为模板接口的一部分,在该模板中它用作约束

#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 < 模板参数列表 >

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 进行替换。否则,折叠扩展约束不满足。
(自 C++26)

[编辑] 约束规范化

约束规范化 是将约束表达式转换为一系列原子约束的合取和析取的过程。表达式 *规范形式* 的定义如下

  • 表达式 (E) 的规范形式是 E 的规范形式。
  • 表达式 E1 && E2 的范式是 E1E2 的范式的合取。
  • 表达式 E1 || E2 的范式是 E1E2 的范式的析取。
  • 表达式 C<A1, A2, ... , AN> 的范式,其中 C 代表一个概念,是 C 的约束表达式的范式,在将 A1A2、...、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
  • 表达式 (E && ...)(... && E) 的范式是一个展开的折叠约束,其中 CE 的范式,折叠操作符是 &&
  • 表达式 (E || ...)(... || E) 的范式是一个展开的折叠约束,其中 CE 的范式,折叠操作符是 ||
  • 表达式 (E1 && ... && E2)(E1 || ... || E2) 的范式分别是
  • (E1 && ...) && E2(E1 || ...) || E2,如果 E1 包含一个未展开的包,或者
  • E1 && (... && E2)E1 || (... || E2),如果上述情况不成立。
(自 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(在 PQ 中的原子约束的标识相同的情况下)。(类型和表达式不会进行等价分析: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++26)

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

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

conceptrequirestypename

[edit] 缺陷报告

以下改变行为的缺陷报告被追溯应用于以前发布的 C++ 标准。

DR 应用于 已发布的行为 正确行为
CWG 2428 C++20 无法对概念应用属性 允许

[edit] 参见

Requires 表达式(C++20) 产生一个类型为 bool 的 prvalue 表达式,描述了约束[edit]