如何利用SFINAE根据模板类型禁用C++类内成员函数?
问题描述
我有一个C++模板类:
template<typename TLoader, typename TCreator> class ManagerGroup { public: uint32_t loadFromPath(const Path &path) { return mLoader.load(path); } void createFile(uint32_t handle) { return mCreator.create(handle); } private: TLoader mLoader; TCreator mCreator; };
我希望当模板参数为std::nullptr_t时,禁用对应的成员函数:
- 当
TCreator为std::nullptr_t时,禁用createFile函数 - 当
TLoader为std::nullptr_t时,禁用loadFromPath函数
期望的使用效果如下:
ManagerGroup<FontLoader, std::nullptr_t> fontManager; // 可正常调用 fontManager.loadFromPath("/test"); // 编译报错 fontManager.createFile(10); ManagerGroup<std::nullptr_t, MeshCreator> meshManager; // 可正常调用 meshManager.createFile(20); // 编译报错 meshManager.loadFromPath("/test");
我尝试用SFINAE实现,但下面的代码没能成功:
template<typename = std::enable_if_t<!std::same<TCreator, std::nullptr_t>::value>> void createFile(uint32_t handle) { return mCreator.create(handle); }
请问是否可以通过SFINAE实现该需求?
解决方案
可以通过SFINAE实现,你之前的代码失败原因是:类模板的成员模板直接使用类模板参数(如TCreator)作为std::same的参数时,编译器不会触发SFINAE规则——因为TCreator是类模板的固定参数,当条件不满足时会直接触发硬错误,而非通过SFINAE剔除该函数重载。
正确实现方式(SFINAE)
将成员函数改为模板函数,让enable_if的条件依赖于成员模板的参数,以此触发SFINAE:
#include <type_traits> template<typename TLoader, typename TCreator> class ManagerGroup { public: // 仅当TLoader不是std::nullptr_t时启用loadFromPath template<typename T = TLoader, std::enable_if_t<!std::is_same_v<T, std::nullptr_t>, int> = 0> uint32_t loadFromPath(const Path &path) { return mLoader.load(path); } // 仅当TCreator不是std::nullptr_t时启用createFile template<typename T = TCreator, std::enable_if_t<!std::is_same_v<T, std::nullptr_t>, int> = 0> void createFile(uint32_t handle) { return mCreator.create(handle); } private: TLoader mLoader; TCreator mCreator; };
关键说明:
- 把成员函数声明为模板函数,引入默认参数
T = TLoader/T = TCreator,让SFINAE的条件依赖于这个成员模板参数 - 当条件不满足时,编译器会通过SFINAE规则自动剔除该函数重载,调用时会直接报“成员函数不存在”的编译错误,完全符合需求
- 若使用C++14及更早版本,需把
std::is_same_v替换为std::is_same<T, std::nullptr_t>::value,std::enable_if_t替换为typename std::enable_if<...>::type
替代方案(C++17 constexpr if)
如果项目支持C++17,也可以用constexpr if配合静态断言实现,不过函数本身依然存在,调用时触发断言错误:
#include <type_traits> template<typename TLoader, typename TCreator> class ManagerGroup { public: uint32_t loadFromPath(const Path &path) { static_assert(!std::is_same_v<TLoader, std::nullptr_t>, "loadFromPath禁用:TLoader为std::nullptr_t"); if constexpr (!std::is_same_v<TLoader, std::nullptr_t>) { return mLoader.load(path); } } void createFile(uint32_t handle) { static_assert(!std::is_same_v<TCreator, std::nullptr_t>, "createFile禁用:TCreator为std::nullptr_t"); if constexpr (!std::is_same_v<TCreator, std::nullptr_t>) { return mCreator.create(handle); } } private: TLoader mLoader; TCreator mCreator; };
这种方式的报错信息更直观,但函数本身并未被剔除,不如SFINAE方案贴合需求。
内容的提问来源于stack exchange,提问作者Gasim
相关产品推荐
相关产品推荐

