Concepts (C++20)
C++20 Concepts
Concepts 是 C++20 引入的核心特性,用于显式约束模板参数,提升代码可读性和错误提示。以下通过代码示例和原理分步骤解析其用法。
1. 基本概念
- 目标:显式声明模板参数必须满足的条件。
- 优势:替代复杂的 SFINAE 和
enable_if
,直接约束类型。
2. 定义 Concept
使用 concept
关键字定义约束条件。
示例 1:检查类型是否可打印
#include <iostream>
#include <concepts>template <typename T>
concept Printable = requires(std::ostream& os, T val) {{ os << val } -> std::same_as<std::ostream&>;
};
- 说明:要求类型
T
必须支持operator<<
输出到流。
3. 应用 Concept 到函数模板
示例 2:约束函数参数可打印
void print(Printable auto val) {std::cout << val << std::endl;
}int main() {print(42); // 合法:int 可打印print("Hello"); // 合法:const char* 可打印// print(std::vector<int>{}); // 错误:vector 不满足 Printable
}
4. 标准库预定义 Concepts
使用 <concepts>
和 <ranges>
中的标准 Concepts。
示例 3:约束算术类型
template <typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;Arithmetic auto square(Arithmetic auto val) {return val * val;
}int main() {square(5); // 合法:int 是整数类型square(3.14); // 合法:double 是浮点类型// square("5"); // 错误:const char* 不满足 Arithmetic
}
5. 组合 Concepts
使用逻辑运算符组合多个 Concepts。
示例 4:约束类型可加且结果匹配
template <typename T>
concept Addable = requires(T a, T b) {{ a + b } -> std::same_as<T>;
};template <Addable T>
T add(T a, T b) {return a + b;
}int main() {add(3, 4); // 合法:int 满足 Addable// add("a", "b"); // 错误:const char* 不满足 Addable
}
6. 在类模板中使用 Concepts
示例 5:约束容器元素可默认构造
#include <concepts>template <std::default_initializable T>
class SafeContainer {T data;
public:SafeContainer() = default;
};int main() {SafeContainer<int> c1; // 合法:int 可默认构造// SafeContainer<std::unique_ptr<int>> c2; // 错误:unique_ptr 不可默认构造
}
7. 复杂约束与嵌套要求
示例 6:约束类型有 size()
方法且返回整型
template <typename T>
concept HasSize = requires(T t) {{ t.size() } -> std::integral;
};void printSize(HasSize auto obj) {std::cout << obj.size() << std::endl;
}struct Vec {size_t size() const { return 10; }
};int main() {Vec v;printSize(v); // 合法:Vec 有 size() 返回 size_t// printSize(42); // 错误:int 没有 size()
}
8. 标准库 Concepts 示例
直接使用标准库中的 Concepts。
示例 7:使用 std::sort
约束可排序范围
#include <vector>
#include <algorithm>
#include <ranges>void sortAndPrint(std::ranges::sortable auto& range) {std::sort(range.begin(), range.end());
}int main() {std::vector<int> vec{5, 3, 1, 4, 2};sortAndPrint(vec); // 合法:vector 支持随机访问迭代器// std::list<int> lst{5, 3}; // sortAndPrint(lst); // 错误:list 迭代器不支持随机访问
}
9. 原理与编译器行为
- 替换检查:编译器在模板实例化时验证 Concept 约束。
- 错误提示:直接指出违反的约束条件,而非模板实例化失败。
- 零开销:所有检查在编译期完成,无运行时负担。
总结
操作 | 语法示例 | 用途 |
---|---|---|
定义 Concept | template<typename T> concept C = ...; | 声明模板参数的显式约束 |
函数模板约束 | void func(C auto param) | 限制参数类型满足 Concept |
组合 Concepts | C1 && C2 或 `C1 | |
类模板约束 | template<C T> class X {}; | 约束类模板参数 |
标准库 Concepts | std::integral<T> , std::sortable | 快速实现通用约束 |
Concepts 显著提升了模板代码的可读性和健壮性,结合现代 C++ 特性(如简写模板),使得泛型编程更加直观高效。
多选题
题目 1:Concepts 的基本约束与函数模板重载
以下代码的输出是什么?
#include <iostream>
#include <concepts>template <typename T>
concept HasValue = requires(T t) { t.value; };struct A { int value; };
struct B {};void process(HasValue auto obj) { std::cout << "HasValue" << std::endl; }
void process(auto obj) { std::cout << "Generic" << std::endl; }int main() {A a;B b;process(a); // 调用哪个版本?process(b); // 调用哪个版本?return 0;
}
A. HasValue
和 Generic
B. Generic
和 Generic
C. 编译失败,重载冲突
D. 运行时错误
题目 2:组合 Concepts 的逻辑
以下代码是否能编译通过?
#include <concepts>template <typename T>
concept Integral = std::integral<T>::value;template <typename T>
concept Floating = std::floating_point<T>::value;template <typename T>
concept Number = Integral<T> || Floating<T>;template <Number T>
T add(T a, T b) { return a + b; }int main() {auto x = add(3, 4); // intauto y = add(3.5, 4.5); // doubleauto z = add("a", "b"); // const char*return 0;
}
A. 编译成功
B. 编译失败,add("a", "b")
不满足 Number
C. 编译失败,Number
的约束定义错误
D. 运行时错误
题目 3:标准库 Concepts 与迭代器约束
以下代码的输出是什么?
#include <vector>
#include <list>
#include <ranges>template <typename T>
void checkRange(T&& range) {if constexpr (std::ranges::random_access_range<T>) {std::cout << "Random Access";} else if constexpr (std::ranges::bidirectional_range<T>) {std::cout << "Bidirectional";} else {std::cout << "Generic";}
}int main() {std::vector<int> vec;std::list<int> lst;checkRange(vec); // 输出什么?checkRange(lst); // 输出什么?return 0;
}
A. Random Access
和 Bidirectional
B. Random Access
和 Generic
C. Bidirectional
和 Bidirectional
D. 编译失败,约束不合法
题目 4:嵌套要求与复杂约束
以下代码是否能编译通过?
#include <concepts>template <typename T>
concept ComplexCheck = requires(T t) {requires std::integral<decltype(t.value)>;{ t.print() } -> std::same_as<void>;
};struct Valid { int value; void print() {} };
struct Invalid1 { double value; void print() {} };
struct Invalid2 { int value; };ComplexCheck auto process(auto obj) { obj.print(); }int main() {process(Valid{}); // 合法?process(Invalid1{}); // 合法?process(Invalid2{}); // 合法?return 0;
}
A. 仅 Valid
合法
B. Valid
和 Invalid1
合法
C. 所有调用均合法
D. 编译失败,ComplexCheck
定义错误
题目 5:Concepts 与模板特化的优先级
以下代码的输出是什么?
#include <iostream>
#include <concepts>template <typename T>
concept Large = sizeof(T) > 4;void process(Large auto t) { std::cout << "Large"; }
void process(auto t) { std::cout << "Generic"; }int main() {process(10); // int(sizeof(int) = 4)process(10L); // long(sizeof(long) = 8)return 0;
}
A. Generic
和 Large
B. Large
和 Large
C. Generic
和 Generic
D. 编译失败,重载冲突
答案与解析
题目 1:Concepts 的基本约束与函数模板重载
答案:A
解析:
process(HasValue auto)
的约束更严格,优先于无约束的process(auto)
。A
满足HasValue
(有value
成员),调用第一个版本;B
不满足,调用第二个版本。- 选项 C 错误:Concepts 约束的函数和非约束函数可以合法重载,优先选择更特化的版本。
题目 2:组合 Concepts 的逻辑
答案:B
解析:
Number
约束T
必须是整数或浮点类型。add("a", "b")
中的const char*
不满足Number
,导致编译失败。- 选项 C 错误:
Number
定义正确,Integral<T> || Floating<T>
语法合法。
题目 3:标准库 Concepts 与迭代器约束
答案:A
解析:
std::vector
的迭代器是随机访问迭代器,输出Random Access
。std::list
的迭代器是双向迭代器,输出Bidirectional
。- 选项 D 错误:标准库 Concepts 的定义合法。
题目 4:嵌套要求与复杂约束
答案:A
解析:
ComplexCheck
要求:t.value
的类型必须满足std::integral
。t.print()
必须存在且返回void
。
- 只有
Valid
满足所有条件:Invalid1
的value
类型是double
,不满足std::integral
。Invalid2
没有print()
方法。
题目 5:Concepts 与模板特化的优先级
答案:A
解析:
process(10)
:int
的sizeof
为 4,不满足Large
,调用Generic
。process(10L)
:long
的sizeof
为 8,满足Large
,调用Large
。- 选项 D 错误:Concepts 约束的函数和非约束函数可以合法重载,无冲突。
总结
通过这组题目,可以深入理解 Concepts 的以下核心机制:
- 重载优先级:约束更严格的函数优先匹配。
- 组合逻辑:通过
&&
和||
组合多个 Concepts。 - 标准库集成:如
std::ranges::random_access_range
。 - 嵌套要求:使用
requires
子句细化约束条件。 - 编译时决策:基于
sizeof
等编译期属性选择实现。