requires 表达式通过占位符变量调用成员函数实现 SFINAE 友好检查,支持参数与返回类型约束、const/volatile 限定符显式声明,避免 std::is_member_function_pointer_v 等不可靠元编程方式。

如何用 requires 表达“必须有某个成员函数”
直接写 requires + 成员访问表达式是最常用、最直观的方式。核心是让编译器能对类型做 SFINAE 友好的检查,而不是靠 static_assert 或运行时判断。
比如要求类型 T 有无参的 begin() 和 end():
template<typename T>
concept Iterable = requires(T t) {
t.begin();
t.end();
};
注意三点:
-
t.begin()不要求返回值类型,只检查能否调用;若需约束返回类型,得加额外子句,比如{ t.begin() } -> std::input_iterator auto; -
t是占位符变量,类型为T,不能写成T::begin()(那是静态成员,且不支持重载) - 如果
T是 const-qualified 类型,而begin()非 const,这个检查会失败——要支持 const 迭代,得显式写const T& t或另加约束
怎么检查带参数或特定签名的成员函数
成员函数重载多、参数类型敏感时,光写 t.func() 不够,容易误判。必须把参数和返回类型都纳入约束。
立即学习“C++免费学习笔记(深入)”;
例如要求 T 有接受 int 并返回 bool 的 match(int):
template<typename T>
concept Matcher = requires(T t, int x) {
{ t.match(x) } -> std::same_as<bool>;
};
关键点:
-
{ t.match(x) }是“复合要求”,括号内是完整表达式,x必须在requires参数列表中声明 -
-> std::same_as<bool>约束返回类型必须恰好是bool(不是可隐式转 bool 的类型) - 若想允许任意可转为 bool 的类型,改用
-> std::convertible_to<bool> - 不写返回类型约束时,仅检查是否可调用,哪怕返回
void也通过
为什么不能直接用 std::is_member_function_pointer_v
因为 std::is_member_function_pointer_v<decltype(&T::func)> 这类元编程方式无法在 concept 中直接使用——它不是编译期可求值的常量表达式,且无法处理重载集、SFINAE 友好性差。
更严重的是:即使 &T::func 能取到地址,也不代表该函数能被对象调用(比如 private、deleted、或参数不匹配)。concept 的 requires 表达式才是模拟真实调用场景的唯一可靠方式。
常见误用:
- 写
requires std::is_invocable_v<decltype(&T::foo), T&>—— 这依赖宏展开和模板元编程,在 concept 定义里非法 - 试图用
std::declval<T>().foo()替代t.foo()——declval在requires中不被允许,且破坏语义清晰性
成员函数 const / volatile 限定符怎么处理
默认的 t.func() 检查的是非 const 对象上的调用。如果类型只提供了 const 版本,这个检查会失败,但用户可能确实希望支持 const 访问。
安全做法是显式覆盖两种情况:
template<typename T>
concept ConstCallable = requires(const T& t) {
t.call();
};
或者合并写:
template<typename T>
concept Callable = requires(T t, const T& ct) {
t.call();
ct.call();
};
注意:
- 不能用
const T t(因为t是左值,const T是右值类型,无法绑定) - 必须用
const T&或const T&&显式声明 const 左值/右值引用 - volatile 几乎不用,但语法同理:
volatile T& vt
实际项目里漏掉 const 支持是高频 bug,尤其当 concept 用于泛型算法时,传入 const 容器却报错,根源往往就在这里。


















