> For the complete documentation index, see [llms.txt](https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/11-yue-shu-yu-gai-nian.md).

# 约束与概念

类模板，函数模板，以及非模板函数（通常是类模板的成员），可以与一项约束（constraint）相关联，它指定了对模板实参的一些要求，这些要求可以被用于选择最恰当的函数重载和模板特化。

这种要求的具名集合被称为概念（concept）。每个概念都是一个谓词，它在编译时求值，并在将之用作约束时成为模板接口的一部分。

## 前言

在 C++20 引入了约束与概念，这一核心语言特性是所有使用模板的 C++ 开发者都期待的。

有了它，我们的模板可以有更多的静态检查，语法更加美观，写法更加容易，而不再需要利用古老的 **SFINAE**。

请务必学习完了上一章节内容；本节会一边为你教学约束与概念的语法，一边用 SFINAE 对比，让你意识到：***这是多么先进、简单的核心语言特性***。

## 定义*概念*（concept）与使用

```cpp

template < 模板形参列表 >
concept 概念名 属性 (可选) = 约束表达式;
```

定义概念所需要的 *约束表达式*，只需要是可以在编译期产生 `bool` 值的表达式即可。

> * 你可以先不看基本概念，关注我们的示例和下面的讲解。

***

> ***我需要写一个函数模板\*\*\*\* ****`add`****，想要要求传入的对象必须是支持\*\*\*\* ****`operator+`**** \*\*\*\*的，应该怎么写？***

此需求就是 `SFINAE` 中提到的，我们使用*概念*（concept）来完成。

```cpp
template<typename T>
concept Add = requires(T a) {
    a + a; // "需要表达式 a+a 是可以通过编译的有效表达式"
};

template<Add T>
auto add(const T& t1, const T& t2){
    std::puts("concept +");
    return t1 + t2;
}
```

我们使用关键字 `concept` 定义了一个*概念*（concept），命名为 `Add`，它的*约束*是 `requires(T a) { a + a; }` 即要求 `f(T a)`、`a + a` 是合法表达式。

```cpp
template<Add T> // T 被 Add 约束
```

语法上就是把原本的 `typename` 、`class` 换成了我们定义的 `Add` *概念*（concept），语义和作用也非常的明确：

* **就是让这个概念约束模板类型形参 `T`，即要求 `T` 必须满足*****约束表达式*****的*****要求序列*** **`T a`** **`a + a`**。如果不满足，则不会选择这个模板。

> "满足"：要求带入后必须是合法表达式；

最开始的概念已经说了：

> *概念*（concept）可以与一项约束（constraint）相关联，它指定了对模板实参的一些要求，这些要求可以被用于选择最恰当的函数重载和模板特化。

另外最开始的概念中还说过：

> 每个概念都是一个**谓词**，它在**编译时求值**，并在将之用作约束时成为模板接口的一部分。

也就是说我们其实可以这样：

```cpp
std::cout << std::boolalpha << Add<int> << '\n';        // true
std::cout << std::boolalpha << Add<char[10]> << '\n';   // false
constexpr bool r = Add<int>;                            // true
```

我相信这非常的好理解，这些语法形式，合理且简单。

*记得我们在第一章节*[*函数模板*](/modern-cpp-templates-cookbook/01-han-shu-mu-ban.md)*中提到的：“C++20 简写函数模板”吗？*

```cpp
decltype(auto) max(const auto& a, const auto& b)  { // const T&
    return a > b ? a : b;
}
```

这段代码来自函数模板那一章节。

> **我想要约束：传入的对象 a b 必须都是整数类型，应该怎么写？**。

```cpp
#include <concepts> // C++20 概念库标头

decltype(auto) max(const std::integral auto& a, const std::integral auto& b) {
    return a > b ? a : b;
}

max(1, 2);     // OK
max('1', '2'); // OK
max(1u, 2u);   // OK
max(1l, 2l);   // OK
max(1.0, 2);   // Error! 未满足关联约束
```

如你所见，我们没有自己定义 *概念*（concept），而是使用了标准库的 [`std::integral`](https://zh.cppreference.com/w/cpp/concepts/integral)，它的实现非常简单：

```cpp
template< class T >
concept integral = std::is_integral_v<T>;
```

这也告诉各位我们一件事情：**定义*****概念*****（concept）** 时声明的约束表达式，只需要是编译期可得到 `bool` 类型的表达式即可。

> 我相信你这里一定有疑问：“那么我们之前写的 requires 表达式呢？它会返回 `bool` 值吗？” 对，简单的说，把模板参数带入到 `requires` 表达式中，是否符合语法，符合就返回 `true`，不符合就返回 `false`。在 [`requires` 表达式](#requires-表达式) 一节中会详细讲解。

它的实现是直接使用了标准库的 `std::is_integral_v<T>`，非常简单。

再谈*概念*（concept）在简写函数模板中的写法 `const std::integral auto& a`，*概念*（concept）只需要写在 `auto` 之前即可，表示此概念约束 `auto` 推导的类型必须为整数类型，语义十分明确，像是 cv 限定、引用等，不需要在乎，或许我们可以先写的简单一点先去掉那些：

```cpp
decltype(auto) max(std::integral auto a, std::integral auto b) {
    return a > b ? a : b;
}
```

这是否直观了很多？并且概念不单单是可以用作简写函数模板中的 `auto`，还有几乎一切语境，比如：

```cpp
int f() { return 0; }

std::integral auto result = f();
```

还是那句话，语义很明确：

* ***概念*****（concept）约束了 `auto` ，它必须被推导为整数类型；如果函数 `f()` 返回类型是 `double`** **`auto` 无法推导为整数类型，那么编译器会报错：“*****未满足关联约束*****”**。

***

类模板同理，如：

```cpp
template<typename T>
concept add = requires(T t){  // 定义概念，通常推荐首字母小写
    t + t;
};

template<add T>
struct X{
    T t;
};
```

变量模板也同理

```cpp
template<typename T>
concept add = requires(T t){
    t + t;
};

template<add T>
T t;

t<int>;     // OK
t<char[1]>; // “t”未满足关联约束
```

将模板中的 `typename` 、`class` 换成 *概念*（concept）即可，表示约束此模板类型形参 `T`。

## `requires` 子句

关键词 requires 用来引入 requires 子句，它指定对各模板实参，或对函数声明的约束。

也就是说我们多了一种使用*概念*（concept）或者说约束的写法。

```cpp
template<typename T>
concept add = requires(T t) {
    t + t;
};

template<typename T>
    requires std::is_same_v<T, int>
void f(T){}

template<typename T> requires add<T>
void f2(T) {}

template<typename T>
void f3(T)requires requires(T t) { t + t; }
{}
```

> `requires` 子句期待一个能够编译期产生 `bool` 值的表达式。

以上示例展示了 `requires` 子句的用法，我们一个个解释

1. `f` 的 `requires` 子句写在 `template` 之后，并空四格，这是我个人推荐的写法；它的约束是：`std::is_same_v<T, int>`，意思很明确，约束 `T` 必须是 int 类型，就这么简单。
2. `f2` 的 `requires` 子句写法和 `f` 其实是一样的，只是没换行和空格；它使用了我们自定义的*概念*（concept）`add`，约束 `T` 必须满足 `add`。
3. `f3` 的 `requires` 子句在函数声明符的末尾元素出现；这里我们连用了两个 `requires`，为什么？其实很简单，我们要区分，第一个 `requires` 是 *`requires` 子句*，第二个 `requires` 是*约束表达式*，它会产生一个编译期的 `bool` 值，没有问题。（如果 `T` 类型带入*约束表达式*是良构，那么就返回 `true`、否则就返回 `false`）。

> 类模板、变量模板等也都同理

requires 子句中，**关键词 requires 必须后随某个常量表达式**。

```cpp
template<typename T>
    requires true
void f(T){}
```

完全可行，各位其实可以直接带入，说白了 `requires` 子句引入的约束表，必须是可以编译期返回 `bool` 类型的值的表达式，我们前面的三个例子：`std::is_same_v`、`add`、`requires 表达式` 都如此。

## 约束

前面我们讲的都是非常基础的*概念*（concept）使用，它们的约束也都十分简单，本节我们详细讲一下。

约束是逻辑操作和操作数的序列，它指定了对模板实参的要求。它们可以在 requires 表达式（见下文）中出现，也可以直接作为概念的主体。

有三种类型的约束：

1. 合取（conjunction）
2. 析取（disjunction）

### 合取

两个约束的合取是通过在约束表达式中使用 && 运算符来构成的：

```cpp
template<class T>
concept Integral = std::is_integral_v<T>;
template<class T>
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;
template<class T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;
```

很合理，**约束表达式**可以使用 `&&` 运算符连接两个约束，只有在两个约束都被满足时才会得到满足

我们先定义了一个 *概念*（concept）Integral，此概念要求整形；又定义了*概念*（concept）SignedIntegral，它的约束表达式用到了先前定义的*概念*（concept）Integral，然后又加上了一个 **`&&`** 还需要满足 std::is\_signed\_v。

*概念*（concept）`SignedIntegral` 是要求有符号整数类型，它的*约束表达式*是：**`Integral<T> && std::is_signed_v<T>`**，就是这个表达式要返回 `true` 才成立，就这么简单。

```cpp
void s_f(const SignedIntegral auto&){}
void u_f(const UnsignedIntegral auto&){}

s_f(1);   // OK
s_f(1u);  // 未找到匹配的重载函数
u_f(1);   // 未找到匹配的重载函数
u_f(1u);  // OK
```

> ***两个约束的合取只有在两个约束都被满足时才会得到满足**。合取从左到右短路求值（如果不满足左侧的约束，那么就不会尝试对右侧的约束进行模板实参替换：这样就会防止出现立即语境外的替换所导致的失败）*。

```cpp
struct X{
    int c{}; // 无意义，为了扩大 X
    static constexpr bool value = true;
};

template<typename T>
constexpr bool get_value() { return T::value; }

template<typename T>
    requires (sizeof(T) > 1 && get_value<T>())
void f(T){}

X x;
f(x); // OK
```

### 析取

两个约束的析取，是通过在约束表达式中使用 || 运算符来构成的：

```cpp
template<typename T>
concept number = std::integral<T> || std::floating_point<T>;
```

和 `||` 运算符本来的意思一样， `std::integral<T>`、`std::floating_point` 满足任意一个，那么整个约束表达式就都得到满足。

```cpp
void f(const number auto&){}

f(1);      // OK 
f(1u);     // OK 
f(1.2);    // OK 
f(1.2f);   // OK 
f("1");    // 未找到匹配的重载函数
```

> *如果其中一个约束得到满足，那么两个约束的析取的到满足。析取从左到右短路求值（如果满足左侧约束，**那么就不会尝试对右侧约束进行模板实参替换**）*。

```cpp
struct X{
    int c{}; // 无意义，为了扩大 X
    //static constexpr bool value = true;
};

template<typename T>
constexpr bool get_value() { return T::value; }

template<typename T>
    requires (sizeof(T) > 1 || get_value<T>())
void f(T){}

X x;
f(x);  // OK 即使 X 根本没有静态 value 成员。
```

如你所见，即使我们的 X 根本不满足右侧约束 `get_value<T>()` 的要求，没有静态 `value` 成员，不过一样可以通过编译。

## `requires` 表达式

> **产生描述约束的 bool 类型的纯右值表达式**。

虽然前面聊*概念*（concept）的时候，用到了 `requires` 表达式（定义 concept 的时候），但是没有详细说明，本节我们详细展开说明。

> *注意，`requires` 表达式 和 `requires` 子句，没关系*。

```txt
requires { 要求序列 }
requires ( 形参列表 (可选) ) { 要求序列 }
```

要求序列，是以下形式之一：

* 简单要求
* 类型要求
* 复合要求
* 嵌套要求

### 解释

要求可以援引处于作用域内的模板形参，形参列表中引入的局部形参，以及在上下文中可见的任何其他声明。

* 将模板参数**代换**到模板化实体的声明中所使用的 requires 表达式中，*可能会导致在其要求中形成无效的类型或表达式，或者违反这些要求的语义*。在这种情况下，requires 表达式的值为 **`false`** 而不会导致程序非良构。
* 按照词法顺序进行**代换**和语义约束检查，当遇到决定 requires 表达式结果的条件时就停止。*如果代换（若存在）和语义约束检查成功*，则 requires 表达式的结果为 **`true`**。

> **简单的说，把模板参数带入到 requires 表达式中，是否符合语法，符合就返回 `true`，不符合就返回 `false`**。

```cpp
#include <iostream>

template<typename T>
void f(T) {
    constexpr bool v = requires{ T::type; }; // 此处可不使用 typename
    std::cout << std::boolalpha << v << '\n';
}

struct X { using type = void; };
struct Y { static constexpr int type = 0; };

int main() {
    f(1);   // false 因为 int::type 不是合法表达式
    f(X{}); // false 因为 X::type   在待决名中不被认为是类型，需要添加 typename
    f(Y{}); // true  因为 Y::type   是合法表达式
}
```

> 三端[测试](https://godbolt.org/z/sxf4PnsfG)。

### 简单要求

简单要求是任何不以关键词 requires 开始的表达式语句。它断言该表达式是有效的。表达式是不求值的操作数；只检查语言的正确性。

```cpp
template<typename T>
concept Addable = requires (T a, T b) {
    a + b; // "需要表达式 a+b 是可以通过编译的有效表达式"
};

template<class T, class U = T>
concept Swappable = requires(T && t, U && u) {
    swap(std::forward<T>(t), std::forward<U>(u));
    swap(std::forward<U>(u), std::forward<T>(t));
};

template<typename T>
    requires (Addable<T> && Swappable<T, T>)
struct Test{};

namespace loser{
    struct X{
        X operator+(const X&)const{
            return *this;
        }
    };
    void swap(const X&,const X&){}
}

int main() {
    using loser::X;

    Test<X> t2; // OK
    std::cout << std::boolalpha << Addable<X> << '\n';     // true
    std::cout << std::boolalpha << Swappable<X,X> << '\n'; // true
}
```

> 以上代码利用了实参依赖查找（`ADL`），即 `swap(X{})` 是合法表达式，而不需要增加命名空间限定。

以关键词 requires 开始的要求总是被解释为[嵌套要求](#嵌套要求)。因此简单要求**不能以没有括号的 requires 表达式开始**。

### 类型要求

类型要求是关键词 **`typename`** 后面接一个可以被限定的**类型名称**。该要求是，所指名的类型是有效的。

可以用来验证：

1. 某个指名的嵌套类型是否存在
2. 某个类模板特化是否指名了某个类型
3. 某个别名模板特化是否指名了某个类型。

```cpp
struct Test{
    struct X{};
    using type = int;
};

template<typename T>
struct S{};

template<typename T>
using Ref = T&;

template<typename T>
concept C = requires{
    typename T::X;      // 需要嵌套类型
    typename T::type;   // 需要嵌套类型
    typename S<T>;      // 需要类模板特化
    typename Ref<T>;    // 需要别名模板代换
};

std::cout << std::boolalpha << C<Test> << '\n'; // true
```

稍微解释一下，类 `Test` 有一个嵌套类 `X`，一个别名 `type`，所以 `typename T::X`、`typename T::type` 类型是有效的。

`typename S<T>` 因为有类模板 `S`，且它接受类型模板参数，所以 `typename S<T>` 类型是有效的。假设模板类 `S` 的模板是接受常量模板参数的，比如 `template<std::size_t> S` ，那么 `typename S<T>` 类型自然不是有效的。

`typename Ref<T>` 因为有别名模板 `Ref`，自然没问题，类型自然是有效的。

> 其实说来说去也很简单，你就直接**带入**，把*概念*（concept）的模板实参（比如 Test）直接带入进去 `requires` 表达式，想想它是不是合法的表达式就可以了。

### 复合要求

复合要求具有如下形式

```txt
{ 表达式 } noexcept(可选) 返回类型要求 (可选) ;
```

> 返回类型要求：-> 类型约束（*概念* concept）

并断言所指名表达式的属性。替换和语义约束检查按以下顺序进行：

1. 模板实参 (若存在) 被替换到 表达式 中；
2. 如果使用了`noexcept`，表达式 一定不能潜在抛出；
3. 如果*返回类型要求* 存在，则：
   * 模板实参被替换到*返回类型要求* 中；
   * `decltype((表达式))` 必须满足*类型约束* 蕴含的约束。否则，被包含的 requires 表达式是 **`false`**。

```cpp
template<typename T>
concept C2 = requires(T x){
    // 表达式 *x 必须合法
    // 并且 类型 T::inner 必须存在
    // 并且 *x 的结果必须可以转换为 T::inner
    {*x} -> std::convertible_to<typename T::inner>;

    // 表达式 x + 1 必须合法
    // 并且 std::same_as<decltype((x + 1)), int> 必须满足
    // 即, (x + 1) 必须为 int 类型的纯右值
    {x + 1} -> std::same_as<int>;

    // 表达式 x * 1 必须合法
    // 并且 它的结果必须可以转换为 T
    {x * 1} -> std::convertible_to<T>;

    // 复合："x.~T()" 是不会抛出异常的合法表达式
    { x.~T() } noexcept;
};
```

我们可以写一个满足*概念*（concept）`C2` 的类型：

```cpp
struct X{
    int operator*()const { return 0; }
    int operator+(int)const { return 0; }
    X operator*(int)const { return *this; }
    using inner = int;
};
```

```cpp
std::cout << std::boolalpha << C2<X> << '\n'; // true
```

[测试](https://godbolt.org/z/vWhcPjbe7)。

**析构函数比较特殊**，不需要我们显式声明它为 `noexcept` 的，它默认就是 `noexcept` 的。

不管编译器为我们生成的 `X` 析构函数，还是我们用户显式定义的 `X` 析构函数，默认都是有 `noexcept` 的。只有我们用户定义析构函数的时候把它声明为了 `noexcept(false)` 这个析构函数才不是 `noexcept` 的，才会不满足 *概念*（concept）`C2` 的要求。

### 嵌套要求

嵌套要求具有如下形式

```txt
requires 约束表达式 ;
```

它可用于根据本地形参指定其他约束。*约束表达式* 必须由被替换的模板实参（若存在）满足。将模板实参替换到嵌套要求中会导致替换到 *约束表达式* 中，但仅限于确定是否满足 *约束表达式* 所需的程度。

```cpp
template<typename T>
concept C3 = requires(T a, std::size_t n) {
    requires std::is_same_v<T*, decltype(&a)>;     // 要求 is_same_v          求值为 true
    requires std::same_as<T*, decltype(new T[n])>; // 要求 same_as            求值为 true
    requires requires{ a + a; };                   // 要求 requires{ a + a; } 求值为 true
    requires sizeof(a) > 4;                        // 要求 sizeof(a) > 4      求值为 true
};
std::cout << std::boolalpha << C3<int> << '\n';    // false
std::cout << std::boolalpha << C3<double> << '\n'; // true
```

> 嵌套要求的 *约束表达式*，只要能编译期产生 `bool` 值的表达式即可，*概念*（concept）、[类型特征](https://zh.cppreference.com/w/cpp/meta#.E7.B1.BB.E5.9E.8B.E7.89.B9.E5.BE.81)的库、`requires` 表达式，等都一样。

这里用 `std::is_same_v` 和 `std::same_as` 其实毫无区别，因为它们都是编译时求值，返回 `bool` 值的表达式。

在上面示例中 `requires requires{ a + a; }` 其实是更加麻烦的写法，目的只是为了展示 `requires` 表达式是编译期产生 `bool` 值的表达式，所以有可能会有**两个 `requires`连用的情况**；我们完全可以直接改成 `a + a`，效果完全一样。

## 部分（偏）特化中使用*概念*

我们在讲 SFINAE 的时候[提到](https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/pages/TITAGOzMxtF3vWA2QXmB#部分偏特化中的-sfinae)了，它可以用作模板偏特化，帮助我们选择特化版本；本节的约束与概念当然也可以做到，并且写法**更加简单直观优美**：

```cpp
#include <iostream>

template<typename T>
concept have_type = requires{
    typename T::type;
};

template<typename T>
struct X {
    static void f() { std::puts("主模板"); }
};

template<have_type T>
struct X<T> {
    using type = typename T::type;
    static void f() { std::puts("偏特化 T::type"); }
};

struct Test { using type = int; };
struct Test2 { };

int main() {
    X<Test>::f();       // 偏特化 T::type
    X<Test2>::f();      // 主模板
}
```

这个示例完全是从 SFINAE 的写法改进而来，我们不需要再写第二个模板类型参数，我们直接写作 `template<have_type T>` 就完成了，概念约束了模板类型参数 `T`。

* **只有概念被满足的时候，才会选择到这个偏特化**。

一些实际的用途，比如我以前的 [`C++20 STL Cookbook`](https://github.com/Mq-b/Cpp20-STL-Cookbook-src#76%E4%BD%BF%E7%94%A8%E6%A0%BC%E5%BC%8F%E5%BA%93%E6%A0%BC%E5%BC%8F%E5%8C%96%E6%96%87%E6%9C%AC) 中对 [`std::formatter`](https://zh.cppreference.com/w/cpp/utility/format/formatter) 进行偏特化，也是使用的概念，[`std::ranges::range`](https://zh.cppreference.com/w/cpp/ranges/range)。

## 总结

我们先讲述了 *概念*（concept）的定义和使用，其中使用到了 `requires` 表达式，但是我们留到了后面详细讲述。

其实本章内容可以划分为两个部分

* 约束与概念
* `requires` 表达式

如果你耐心看完，我相信也能意识到它们是互相掺杂，一起使用的。语法上虽然感觉有些多，但是也都很合理，我们只需要 ***带入***，按照基本的常识判断这是不是符合语法，基本上就可以了。

`requires` 关键字的用法很多，但是划分的话其实就两类

* `requires` 子句
* `requires` 表达式

`requires` 子句和 `requires` 表达式可以连用，组成 `requires requires` 的形式。我们在 [`requires` 子句](#requires-子句)讲过。

还有在 `requires` 表达式中的嵌套要求，也会有 `requires requires` 的形式。

如果看懂了，这些看似奇怪的 `requires` 关键字复用，其实也都很合理，只需要记住最重要的一句话：

> 可以连用 `requires requires` 的情况，都是因为第一个 `requires` 期待一个可以编译期产生 `bool` 值的表达式；而 **`requires` 表达式就是产生描述约束的 bool 类型的纯右值表达式**。
