如何用C++17 SFINAE替代requires检查未被SFINAE禁用的成员函数
问题分析
在C++17中检查带SFINAE条件的成员函数是否存在时,原写法会触发硬错误而非SFINAE过滤。这是因为直接调用std::declval<T>().foo()时,编译器会尝试实例化成员函数模板的默认参数(即std::enable_if部分),当条件不满足时,会直接抛出no type named 'type'错误,而非丢弃无效的模板特化。
解决方案
核心思路是将成员函数的调用尝试放入模板参数推导的SFINAE语境中,让编译器在推导失败时自动丢弃无效的特化,而非触发硬错误。可以通过定义辅助测试函数实现:
#include <type_traits> #include <utility> template <typename T = int> struct A { template <typename = void, std::enable_if_t<std::is_same_v<T, int>, bool> = true> T foo() { return 3; } }; // 辅助测试函数:若T的实例可调用foo(),则匹配此重载 template <class T> auto test_foo(T&& t) -> decltype(std::forward<T>(t).foo()); // Fallback重载:任何无法匹配的情况都会走到这里 template <class> void test_foo(...); template <class, class = void> struct hasFoo : std::false_type {}; // 检测test_foo是否能成功调用,进而判断foo是否存在且可用 template <class T> struct hasFoo<T, std::void_t<decltype(test_foo(std::declval<T>()))>> : std::true_type {}; // C++20的requires写法(保留作对比) template <class T> struct hasFoo20 : std::bool_constant< requires(T t) { t.foo(); } > {}; // 验证断言 static_assert(hasFoo<A<>>::value); static_assert(!hasFoo<A<double>>::value); static_assert(hasFoo20<A<>>::value); static_assert(!hasFoo20<A<double>>::value);
原理说明
- 当尝试实例化
hasFoo<T>时,编译器会检查decltype(test_foo(std::declval<T>()))是否有效:- 若
T的实例可以调用foo()(如A<>),则第一个test_foo重载匹配成功,void_t有效,hasFoo<T>继承true_type。 - 若
T的实例无法调用foo()(如A<double>),第一个test_foo重载的推导会因foo()的SFINAE条件不满足而失败,此时编译器会匹配第二个可变参数的test_foo重载,但decltype(void)是无效的,因此hasFoo<T>继承false_type,且不会触发硬错误。
- 若
这种写法利用了C++17的SFINAE规则,将成员函数的有效性检测转化为辅助函数的重载匹配问题,避免了直接实例化成员函数模板导致的硬错误。
内容的提问来源于stack exchange,提问作者rath3t
相关产品推荐
相关产品推荐

