C++20 concepts:把模板要求写在入口,而不是等一屏报错
模板函数若只能接收特定类型,最好直接表达这个要求。concepts 让约束成为接口的一部分,调用错误通常也能更早定位到"不满足什么条件"。
最低标准:C++20。示例分别约束整数类型和可求 size 的对象。
1. 命名约束,让调用意图可读
cpp
#include <concepts>
#include <cstddef>
#include <iostream>
#include <string>
#include <type_traits>
template<class T>
concept NonBoolIntegral = std::integral<T> &&
(!std::same_as<std::remove_cv_t<T>, bool>);
template<NonBoolIntegral T>
constexpr bool is_even(T value) {
return value % 2 == 0;
}
template<class T>
concept HasSize = requires(const T& value) {
{ value.size() } -> std::convertible_to<std::size_t>;
};
template<HasSize T>
void show_size(const T& value) {
std::cout << value.size() << '\n';
}
int main() {
static_assert(NonBoolIntegral<int>);
static_assert(!NonBoolIntegral<bool>);
std::cout << std::boolalpha << is_even(6) << '\n';
show_size(std::string{"hello"});
}
输出是 true 和 5。is_even 不接受浮点数,也主动排除 bool;这是业务选择,而不是 std::integral 自带的限制。
2. requires 检查表达式是否适用
HasSize 表示:对 const 对象调用 size 的表达式合法,而且结果能够转换成 size_t。requires 表达式本身不会为了检查约束而执行这次调用。
注意 const 和引用限定的影响:如果某个类型只有非 const 的 size 成员,就不满足这里定义的 HasSize。约束应与函数实际使用对象的方式一致,不宜随意放宽。
3. 语法通过不代表业务一定正确
一个对象完全可以有返回错误数值的 size 函数,仍然通过上述表达式检查。类似地,比较器的调用形式符合要求,也不代表编译器能证明它满足严格弱序。
概念表达了可检查的结构要求,但业务语义、有效值范围、资源状态和线程安全仍然需要文档、运行时校验和测试。
4. 从小而明确的约束开始
尽量使用标准已有的概念,再按确实需要的能力组合。不要为了一个只会调用 size 的函数,要求参数必须拥有几十个无关成员;过度约束会排除本来可以工作的类型。
concepts 也不是类继承接口。一个类型不必显式声明"实现某个 concept",只要满足约束即可。它特别适合泛型算法、容器工具函数和库接口,能把隐含约定变成读者看得见的入口条件。