CTTCG 附录 E Concept

定义和使用 concept

下面演示了几种 C++20 支持的定义 concept 的方式,每一条约束都是以下之一,然后用分号结尾:

  1. 类型
  2. 表达式
  3. { 表达式 } noexcept -> 检查返回类型是否满足其他约束(这个 noexcept 可以不要)
  4. 引入其他 requires 表达式
template <typename T>
concept StringConcept = requires(T t) {
    typename T::iterator; // 1. 检查类型存在性
    t.data();             // 2. 检查表达式合法性

    // 3. 将表达式作为 concept 的第一个参数,要求 concept 成立(额外参数从第二个参数起放置)
    { t.c_str() } -> std::same_as<char const *>;

    // 4. 用 requires 语句引入子条件
    requires std::is_pointer_v<decltype(t.data())>;

    // 5. 除了 requires 体之外还能用其他编译期常量表达式做约束
} && (sizeof(T) > 8) && !std::is_aggregate_v<T>;

template<StringConcept Str>
void takeString(Str const &s) { }

int main() {
    takeString(std::string{"hello"});
}

在模板中加上 requires 条件则相对比较简单。只需要在 模板参数后,函数返回值前面,或者 函数体前面 加上 requires 约束。requires 约束能使用一般的条件表达式,也能把 concept 当成 type trait 使用。比如:

template <typename T>
requires HasPlus<T>
int f(T p)
{}

template <typename T>
int g(T p)
requires HasPlus<T>
{}

// 注意 requires 约束的位置比较灵活

上方代码中 template<StringConcept Str> 是对 template<typename Str> requires StringConcept<Str> 的缩写。只有单参数 concept 可以用这种简写方法。(是的,concept 可以有多个参数!

requires 表达式可以作为函数 requires 要求的一部分,形式上有两个连着的 requires 关键字,但是它们的含义不同:

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

concept 下的重载选择

如果一个 concept 中显式使用了另外一个 concept(可以通过 requires 体中的 requires 语句,也可以通过 concept 定义中的 && 逻辑连词),则前者比后者严格。如果两个 concept 都能启用模板,则更严格的那个被选择。