C++20类模板成员函数中前置与后置requires的SFINAE差异
C++20中带requires的成员函数模板与普通成员函数的行为差异及使用要点
问题背景
我有一段C++17的SFINAE代码:
#include <type_traits> template<typename> constexpr bool dependentFalse = false; template<typename T> struct IAmEmpty { template<typename U=T, std::enable_if_t<dependentFalse<U>>* =nullptr> auto deleteMe() {} }; template struct IAmEmpty<int>;
我尝试将其改写为C++20版本,写出了如下代码:
#include <type_traits> template<typename T> struct IAmEmpty { template<typename U=T> requires false void deleteMe() {} void deleteMeToo() requires false {} }; template struct IAmEmpty<int>;
我的理解是:deleteMe是成员函数模板,而deleteMeToo并非函数模板,这个理解是否正确?另外,deleteMe与deleteMeToo是否存在实际行为差异?在选择前置或后置requires时需要注意哪些要点?
回答
1. 关于你的理解是否正确
没错,你的理解完全正确:
deleteMe是成员函数模板:它带有自己的模板参数U(哪怕有默认值T),属于模板范畴。deleteMeToo是普通非模板成员函数:它没有模板参数列表,只是在函数声明后附加了requires约束。
2. 两者的实际行为差异
最核心的差异体现在模板实例化规则和SFINAE的适用范围上:
- 对于
deleteMe(函数模板):
当显式实例化IAmEmpty<int>时,函数模板的约束requires false会触发SFINAE——编译器会直接丢弃这个函数模板的特化,不会报错,最终生成的IAmEmpty<int>结构体里不会包含deleteMe的任何实例。这和你C++17版本用enable_if实现的效果完全一致。 - 对于
deleteMeToo(非模板成员函数):
当显式实例化IAmEmpty<int>时,编译器必须检查这个非模板函数的约束。由于requires false是一个永远不满足的常量表达式约束,编译器会直接报错,因为非模板函数的约束不满足时无法触发SFINAE(SFINAE只适用于模板的替换失败)。
简单说:你的C++20代码里,显式实例化IAmEmpty<int>时,deleteMe不会导致编译错误,而deleteMeToo会直接编译失败。
3. 选择前置/后置requires的注意要点
前置requires(用于模板)
- 适用场景:模板函数/类模板的成员函数模板,用来在模板参数列表之后直接约束模板的实例化。
- 核心特性:约束属于模板的一部分,替换失败时触发SFINAE,不会导致编译错误,只会丢弃不符合约束的模板特化。
- 注意:如果是给类模板的成员函数模板加前置
requires,约束可以依赖于类模板参数和成员函数自身的模板参数。
后置requires(可用于模板或非模板函数)
- 对于模板函数:后置
requires和前置requires效果基本一致,只是写法不同,约束同样会触发SFINAE。 - 对于非模板函数:后置
requires是对函数的直接约束,一旦约束不满足,编译器会直接报错(因为没有模板替换过程,无法触发SFINAE)。这种写法通常用于:- 类模板的非模板成员函数,约束依赖于类模板的参数(此时类模板实例化时才会检查约束);
- 全局非模板函数,用来限制函数的调用条件(不满足时直接编译报错)。
总结:如果你的目标是实现类似C++17中SFINAE的“条件性丢弃函数”效果,必须用带模板参数的成员函数+requires约束(比如deleteMe的写法);如果用非模板成员函数加requires,约束不满足时会直接报错,无法达到SFINAE的效果。
内容的提问来源于stack exchange,提问作者alagner
相关产品推荐
相关产品推荐

