唯一干净可读的c++20路径是requires表达式:检查公有成员用requires(t t) { t.id; },嵌套类型用requires { typename t::value_type; },二者语法不可互换。

要用 C++20 Concepts 限制模板函数参数必须具有某成员(比如 id、value_type、size()),唯一干净、可读、可诊断的路径就是写 requires 表达式——不是 static_assert,不是硬写 typename T::value_type,也不是靠编译错误倒推。
检查公有成员变量是否存在:用 requires(T t) { t.member; }
这是最常用也最容易写错的场景。比如要求类型必须有公有成员 id:
-
requires(T t) { t.id; }是对的:它验证“给一个T类型的对象,能否用点号访问id”,隐含要求id是公有、非静态、非函数、非模板成员 - 错写成
requires { typename T::id; }会去查嵌套类型别名,不是成员变量 - 错写成
requires { T::id; }会去查静态成员,漏掉非静态实例成员 - 如果
id是private或protected,这个requires直接失败——这不是 bug,是设计使然:Concepts 约束的是接口可见性,不是实现细节 - 示例:
template<typename t> concept HasId = requires(T t) { t.id; };</typename>然后template<hasid t> void log(const T& x) { std::cout </hasid>
检查成员函数签名是否匹配:注意 const / 返回类型 / 重载
检查 size() 这类成员函数,不能只写 t.size();,否则无法控制调用上下文和返回语义:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 要支持
const对象调用,得写requires(const T& t) { t.size(); };只写requires(T t)可能匹配不到const成员 - 只写
{ t.size(); }只保证能调用,不约束返回值;若要求返回std::size_t,必须加箭头:{ t.size() } -> std::same_as<:size_t></:size_t> - 若类型有多个重载的
size()(比如带默认参数或 const/non-const 版本),requires会接受任一可行重载,不报歧义——这是预期行为 - 不要写
requires { T::size(); }:那是查静态成员函数,不是实例成员
检查嵌套类型存在性:用 typename T::xxx,别用 t.xxx
当你要约束的是类型别名(如 value_type、iterator),而不是变量或函数,就得换语法:
- 正确:
requires { typename T::value_type; }—— 只检查该嵌套类型声明是否存在,不构造、不访问、不关心私有公有 - 错误:
requires(T t) { t.value_type; }——value_type不是对象成员,点号语法非法 - 错误:
requires { T::value_type; }—— 缺少typename,编译器会当成静态成员变量或枚举值 - 如果还需进一步约束
value_type本身(比如必须是整型),要额外加一层:requires std::integral<typename t::value_type></typename> - 这种写法对
private嵌套类型也有效,因为typename T::xxx是纯粹的名称查找,不涉及访问控制
C++17 兼容方案:用 std::void_t + 成员指针探测
如果你的项目还卡在 C++17,又不想写一长串 SFINAE trait,可以用标准方式做成员变量存在性检测:
- 核心是
decltype(&T::member):只要该表达式合法且结果是对象指针类型,就说明成员存在 - 必须包裹在
std::void_t里才能触发 SFINAE,否则编译器会在偏特化前就报错 - 标准写法:
template<typename t typename="void"> struct HasMemberId : std::false_type {};</typename>template<typename t> struct HasMemberId<t std::void_t>> : std::is_member_object_pointer<decltype> {};</decltype></t></typename> - 然后在函数模板中用:
template<typename t std::enable_if_t>::value>* = nullptr></typename> - 注意:
&T::id能识别mutable、const、引用成员,但无法区分函数和对象——所以得靠std::is_member_object_pointer过滤
真正容易被忽略的是:Concepts 的 requires 表达式不是运行时求值,也不构造对象;它只做语法可行性检查。这意味着哪怕 t.id 在运行时会抛异常、或依赖未定义行为,只要点号访问在语法上成立,约束就通过。反过来,如果成员存在但不可见(比如私有且无友元),requires(T t) { t.id; } 就会失败——这不是缺陷,而是强制你把接口契约显式暴露出来。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










