# 约束与概念

类模板，函数模板，以及非模板函数（通常是类模板的成员），可以与一项约束（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 类型的纯右值表达式**。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mq-bai.gitbook.io/modern-cpp-templates-cookbook/11-yue-shu-yu-gai-nian.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
