前言:requires 是什么? 如果说 Concepts 是 C++20 约束的顶层语法 ,那么 requires 就是它的底层引擎 。你可以在 requires 子句中使用 Concept,也可以在 requires 表达式中用更自由的方式定义约束。
本文重点 :深入解析 requires 表达式——一种在编译期检查类型能力 的 DSL(领域特定语言)。
一、两种 requires:你分得清吗? C++20 引入了 requires 的两种用法,它们看起来相似,但本质不同:
1.1 requires 子句(requires Clause) 放在模板参数列表后、函数声明前,筛选 是否启用该模板:
1 2 3 template <typename T> requires std::integral<T> void func (T n) { }
1.2 requires 表达式(requires Expression) 放在 concept 定义或函数体中,检查 某个表达式是否对给定类型合法:
1 2 3 4 template <typename T>concept Addable = requires (T a, T b) { a + b; };
一句话区分 :requires 子句是”条件”,requires 表达式是”检查”。
二、requires 表达式详解 2.1 基本语法 1 2 3 requires (parameter-list) { requirements }
简单形式 requires { expr; }——检查表达式是否合法带参数形式 requires(T a, T b) { a + b; }——使用具体参数检查复合形式 requires(T a) { { *a } -> convertible_to<void>; }——检查表达式及其属性2.2 简单约束示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <concepts> #include <type_traits> template <typename T>concept Addable = requires (T a, T b) { a + b; a - b; }; template <typename T>concept Comparable = requires (T a, T b) { { a <=> b } -> std::convertible_to<int >; }; int main () { static_assert (Addable<int >); static_assert (Addable<double >); static_assert (Comparable<int >); }
2.3 原子约束(Atomic Constraints) requires 表达式中的每个简单表达式 就是一个原子约束 。编译器会独立检查每个原子约束。
1 2 3 4 5 6 template <typename T>concept Addable = requires (T a, T b) { a + b; a - b; a * b; };
原子约束的特点 :
独立求值 :每个原子约束失败不会影响其他约束不包含控制流 :只能是表达式,不能是 if、for 等语句惰性求值 :只有在模板实例化时才会检查三、约束组合与优先级 3.1 使用 && 和 || 组合 Concept 1 2 3 4 5 6 7 8 9 #include <concepts> template <typename T>concept AddableAndComparable = Addable<T> && Comparable<T>;template <typename T>concept Numeric = std::integral<T> || std::floating_point<T>;
3.2 requires 子句中的约束组合 1 2 3 4 5 6 7 8 template <typename T> requires Addable<T> && Comparable<T> void process (T v) { }template <typename T> requires (std::integral<T> || std::floating_point<T>) void process_numeric (T v) { }
四、实战:带约束的函数重载 4.1 基于 Concept 的重载分派 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <concepts> template <std::integral T>void process (T v) { std::cout << "integral: " << v << "\n" ; } template <std::floating_point T>void process (T v) { std::cout << "floating: " << v << "\n" ; } template <std::same_as<std::string>>void process (std::string v) { std::cout << "string: " << v << "\n" ; } int main () { process (42 ); process (3.14 ); process (std::string ("hello" )); }
4.2 partial specialization + requires 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <concepts> #include <type_traits> template <typename T, typename = void >struct Serializer { static void serialize (T val) { std::cout << "generic: " << val << "\n" ; } }; template <typename T>struct Serializer <T, std::enable_if_t <std::integral<T>>> { static void serialize (T val) { std::cout << "integer: " << val << "\n" ; } }; template <typename T>struct Serializer <T, std::enable_if_t <std::floating_point<T>>> { static void serialize (T val) { std::cout << "float: " << val << "\n" ; } }; int main () { Serializer<int >::serialize (42 ); Serializer<double >::serialize (3.14 ); Serializer<std::string>::serialize ("hi" ); }
注意 :C++20 的 requires 子句让 partial specialization 的约束更加直观:
1 2 3 4 5 template <typename T> requires std::integral<T> struct Serializer <T> { };
五、复合约束:检查表达式属性 5.1 约束继承(Constraint Checking) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <concepts> template <typename T>concept Dereferenceable = requires (T p) { { *p } -> std::convertible_to<typename T::value_type>; }; template <typename T>concept Container = requires (T c) { { c.begin () } -> std::input_iterator; { c.end () } -> std::input_iterator; { c.size () } -> std::integral; };
5.2 嵌套要求(Nested Requirements) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <concepts> #include <type_traits> template <typename T>concept ComplexNumber = requires (T a, T b) { { a + b } -> std::convertible_to<T>; { a - b } -> std::convertible_to<T>; typename T::real_type; requires std::floating_point<typename T::real_type>; };
六、requires 表达式的求值规则 6.1 何时求值? requires 表达式是惰性求值 的——只有在模板实例化时,约束被检查时才会真正求值。
1 2 3 4 5 6 7 8 9 10 11 12 template <typename T>concept Bad = requires (T x) { x.nonExistentMethod (); }; int main () { Bad<int >* p = nullptr ; }
6.2 SFINAE 友好的约束检查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <concepts> template <typename T>concept HasValueType = requires { typename T::value_type; }; template <HasValueType T>void print_type_info () { std::cout << "has value_type\n" ; } template <typename T>void print_type_info () { std::cout << "no value_type\n" ; } int main () { print_type_info <int >(); print_type_info<std::vector<int >>(); }
七、常见错误与避坑指南 7.1 约束检查的”短路”行为 1 2 3 4 5 template <typename T>concept Weird = requires (T a) { a.f (); a.g (); };
问题 :如果 a.f() 失败,后续约束不会被检查。
7.2 类型区分 vs 值区分 1 2 3 4 5 6 7 8 9 10 11 template <typename T>concept HasValueType = requires { typename T::value_type; }; template <typename T>concept HasValue = requires (T obj) { obj.value; };
八、与 Concepts 的关系 graph LR
A["🟣 Concept 语法"] --> B["🔵 顶层约束声明"]
A --> C["🟡 底层实现"]
C --> D["requires 子句"]
C --> E["requires 表达式"]
D --> F["template<typename T><br/>requires std::integral<T>"]
E --> G["concept Addable =<br/> requires(T a, T b)<br/> { a + b; };"]
style A fill:#E8D5F5,stroke:#CE93D8,color:#333
style B fill:#C7CEEA,stroke:#9FA8DA,color:#333
style C fill:#FFDAB9,stroke:#FFAB76,color:#333
style D fill:#B5EAD7,stroke:#80CBC4,color:#333
style E fill:#B5EAD7,stroke:#80CBC4,color:#333
style F fill:#FFF9C4,stroke:#F9A825,color:#333
style G fill:#FFF9C4,stroke:#F9A825,color:#333九、完整的编译示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <concepts> #include <type_traits> template <typename T>concept Addable = requires (T a, T b) { a + b; a - b; }; template <typename T>concept Comparable = requires (T a, T b) { { a <=> b } -> std::convertible_to<int >; }; template <std::integral T>void process (T v) { std::cout << "integral: " << v << "\n" ; }template <std::floating_point T>void process (T v) { std::cout << "floating: " << v << "\n" ; }template <typename T>struct Wrapper { static_assert (std::is_integral_v<T>, "T must be integral" ); T value; }; int main () { process (42 ); process (3.14 ); static_assert (Addable<int >); static_assert (Addable<std::string>); }
运行结果 :
1 2 integral: 42 floating: 3.14
十、总结 特性 requires 子句 requires 表达式 位置 模板参数列表后 concept 定义或函数体 作用 筛选是否启用模板 检查类型能力 返回值 布尔值 布尔值 组合 && ||&& ||
记住 :requires 表达式是 C++20 约束系统的”显微镜”——它让你在编译期观察类型的每一个”能力”,并据此做出精确的分派决策。
📚 C++20 新特性 系列导航 本文是《C++20 新特性》系列第 2/7 篇。
📖 全部 7 篇目录(点击展开) (一)Concepts (二)requires 表达式 ← 当前 (三)Spaceship Operator (四)Lambda 加强 (五)consteval 与 constinit (六)Coroutine 协程 (七)Ranges 库