基于SFINAE的C++成员变量检测失效问题排查
这段C++代码尝试用SFINAE机制检测类是否包含age成员变量,但即使是包含公共age的PublicPerson,检测结果也始终返回false;而用相同思路检测类中的pointer类型别名却能正常工作。代码如下:
#include <iostream> #include <type_traits> #include <utility> template <typename> struct Void { using type = void; }; template <typename T, typename U = void> struct HasAge : std::false_type {}; template <typename T> struct HasAge<T, typename Void<decltype(std::declval<T>().age)>::type> : std::true_type {}; struct Empty {}; struct PrivatePerson { private: int age; }; struct PublicPerson { public: PublicPerson() = default; int age; }; template <typename T, typename = void> struct HasPointer : std::false_type {}; template <typename T> struct HasPointer<T, typename Void<typename T::pointer>::type> : std::true_type {}; struct FancyPointer { using pointer = int; }; struct HazardPointer { template <typename T> struct pointer {}; }; int main() { using namespace std; cout << boolalpha; cout << HasAge<Empty>::value << endl; // false cout << HasAge<PrivatePerson>::value << endl; // false cout << HasAge<PublicPerson>::value << endl; // false cout << HasPointer<FancyPointer>::value << endl; // true cout << HasPointer<HazardPointer>::value << endl;// false }
失效原因解析
核心问题:右值无法访问非const非静态成员
std::declval<T>()默认返回T&&类型的右值引用,而C++语法明确禁止通过右值直接访问非const的非静态成员变量——这是为了避免开发者误修改临时对象的成员(临时对象生命周期短,修改后大概率无法被有效使用)。因此检测PublicPerson时,decltype(std::declval<PublicPerson>().age)会触发语法错误,导致模板特化推导失败,编译器只能匹配主模板,最终返回false。对比
HasPointer的正常逻辑HasPointer中检测的是typename T::pointer,这是访问类的类型别名,不管T是左值还是右值,类型别名的访问不受值类别限制,所以只要类存在pointer类型别名,模板特化就能正常推导,SFINAE机制正确触发。
修复方案
把std::declval<T>()改为返回左值引用std::declval<T&>(),这样就能通过左值访问非const成员变量,SFINAE会正确匹配特化模板:
template <typename T> struct HasAge<T, typename Void<decltype(std::declval<T&>().age)>::type> : std::true_type {};
另外补充:PrivatePerson的age是私有成员,无论用左值还是右值访问都会触发访问权限错误,这种错误属于模板替换失败范畴,因此HasAge<PrivatePerson>::value返回false是符合预期的。
内容的提问来源于stack exchange,提问作者Guo Cheng

