传入派生类成员函数指针为模板参数时,如何捕获其所属类类型?
嘿,这个坑我之前踩过!你遇到的问题本质是C++的隐式转换在搞鬼:派生类继承来的成员函数指针,可以自动转换成基类的成员函数指针,而模板参数推导会优先选择这个转换后的类型,所以才会输出CObject而不是CDerivedObject。
先还原一下你的场景(我猜你的代码大概是这样的):
#include <iostream> #include <typeinfo> class CObject { public: void setID(int) {} }; class CDerivedObject : public CObject {}; template<typename _T> void function_template_test(void (_T::*func)(int)) { std::cout << typeid(_T).name() << std::endl; } int main() { function_template_test(&CDerivedObject::setID); return 0; }
编译运行后输出7CObject,完全不符合预期对吧?
核心原因
&CDerivedObject::setID的原始类型是void (CDerivedObject::*)(int),但因为setID是从CObject继承来的,这个指针可以隐式转换成void (CObject::*)(int)。模板推导的时候,编译器会优先选择这个转换后的类型来匹配模板参数_T,所以就得到了基类类型。
解决方案:捕获原始成员函数指针类型
要拿到派生类类型,我们得绕过这个隐式转换,直接处理原始的成员函数指针类型,再从中提取所属的类。这里有两种实用的方法:
方法1:自定义类型萃取工具(C++11及以上可用)
我们可以写一个模板特化的类型萃取器,专门从成员函数指针里提取所属的类:
#include <iostream> #include <typeinfo> class CObject { public: void setID(int) {} }; class CDerivedObject : public CObject {}; // 类型萃取模板:提取成员函数指针的所属类 template<typename MemFunc> struct member_function_class; // 特化版本:匹配非const成员函数指针 template<typename T, typename Ret, typename... Args> struct member_function_class<Ret (T::*)(Args...)> { using type = T; }; // 方便使用的别名 template<typename MemFunc> using member_function_class_t = typename member_function_class<MemFunc>::type; // 修改后的模板:直接接收成员函数指针类型 template<typename MemFunc> void function_template_test(MemFunc func) { using DerivedType = member_function_class_t<MemFunc>; std::cout << typeid(DerivedType).name() << std::endl; } int main() { function_template_test(&CDerivedObject::setID); return 0; }
现在编译运行,就会输出派生类的类型(比如14CDerivedObject,具体输出取决于编译器的name mangling规则)。
方法2:C++17的auto模板参数(更简洁)
如果你的编译器支持C++17,还可以用auto模板参数直接捕获成员函数指针,再搭配萃取工具:
#include <iostream> #include <typeinfo> class CObject { public: void setID(int) {} }; class CDerivedObject : public CObject {}; // 复用上面的类型萃取器 template<typename MemFunc> struct member_function_class; template<typename T, typename Ret, typename... Args> struct member_function_class<Ret (T::*)(Args...)> { using type = T; }; template<typename MemFunc> using member_function_class_t = typename member_function_class<MemFunc>::type; // 用auto模板参数直接接收成员函数指针 template<auto MemFunc> void function_template_test() { using DerivedType = member_function_class_t<decltype(MemFunc)>; std::cout << typeid(DerivedType).name() << std::endl; } int main() { // 调用时直接把成员函数指针作为模板参数传入 function_template_test<&CDerivedObject::setID>(); return 0; }
这种写法更直观,直接把成员函数指针固定为模板参数,完全避免了隐式转换的问题。
总结
关键就是不要让模板推导去匹配基类的成员函数指针类型,而是直接捕获原始的派生类成员函数指针,再通过类型萃取提取出所属的类类型,这样就能得到你想要的CDerivedObject了。
内容的提问来源于stack exchange,提问作者Jordi Espada

