C++20 concepts

使用 concepts 的4种方式

template < class T >
concept floating_point = std::is_floating_point_v<T>;

一般 requires 从句

将 requires 从句放在 模板参数列表函数返回值 之间。

template <typename T>

requires floating_point<T> // requires 从句的表示形式1 = requires + 约束表达式(concept / type trait)

auto add(T a, T b) {
  return a + b;
}

template <typename T>

requires requires() { // requires 从句的表示形式2 = requires + requires 表达式
  requires (!std::is_floating_point_v<T>);
}

auto add(T a, T b) {
  return a + b;
}

尾置 requires 从句

将 requires 从句放在 函数签名函数体 之间(紧挨着花括号,在任何修饰词之后)。

template <typename T>
auto add(T a, T b) requires floating_point<T> {
  return a + b;
}

模板参数约束

使用 concept 名声明模板参数来对模板参数进行约束。

template <floating_piont T>
auto add(T a, T b) {
  return a + b;
}

模板参数声明缩写形式

声明模板参数的简写形式,置于 auto 左侧。

auto add(floating_point auto a, floating_point auto b) {
  return a + b;
}

对于将 concept 应用到类来说,无法使用 尾置 requires 从句模板参数声明缩写形式

编写自己的 concepts

组合已定义好的 concepts

使用关系运算符 ||&& 来将多个 concept 组合成一个。

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

!注意! 对于非运行(!)需要包裹在括号内,如:requires (!std::same_as<int>)

要求模板参数必须支持的操作

requires 表达式看起来像一个 lambda 表达式,requires 伴随在参数列表的左侧,然后是函数体。参数列表描述需要支持特定需求的模板参数,函数体描述需要支持的要求。

template <typename T>
concept addable = requires(T a, T b) { // = 右侧是一个 requires 表达式
  a + b; 
};

要求模板参数必须存在特定接口

template <typename T>
concept has_size = requires(T t) {
  t.size();
}

要求模板参数的特定接口必须存在特定返回值类型 (即组合要求)

template <typename T>
concept has_size = requires(T t) {
  {t.size()} -> std::same_as<size_t>;
}

要求模板参数必须存在特定的类型要求

template<typename T> 
using reference = T&;

template<typename T>
concept type_requirement = requires {
  typename T::value_type;
  typename other<T>;
  typenmae reference<T>;
};

嵌套 requires 从句

template <typename C>
concept clonable = requires(C c) {
    c.clone();
    requires std::same_as<C, decltype(c.clone())>; // 内嵌的 requires 从句
};

Concept 模板参数推导

template <typename Base, typename Exponent>
concept has_power = (std::integral<Exponent> || std::floating_point<Exponent>) && 
                    requires (Base base, Exponent exponent) {
                      base.power(exponent);
                    };

template<typename Exponent> 
void print(has_power<Exponent> auto number, Exponent exponent) {
  std::cout << number.power(exponent) << '\n';
}

通过匹配先推导出指数 Exponent 的类型,使得 has_power 只剩底数类型未知,再通过实参匹配 number 推导出底数类型。

Reference