C++中如何从成员函数指针提取类型参数包实现通用invoke函数
你这个可变参的实现思路完全可行,只要修正两个核心问题就能实现零冗余的通用invoke
你现在写的代码跑不起来,本质是踩了C++模板的两个常见坑:
- 参数包不能用typedef存储。C++规范里可变参数包不能通过typedef做中转存储,你写的
typedef Args Arguments根本没法让编译器识别出后续展开的是参数列表,写typename FunctionPointer<Func>::Arguments ... params的时候会直接报语法错。要留存参数列表,要么用std::tuple封装参数包,要么在偏特化场景下直接透传参数包类型。 - 模板推导的非推导上下文问题。你定义的invoke模板里,Func是嵌套依赖类型
FunctionPointer<Func>的模板参数,属于C++标准规定的非推导上下文——编译器在推导模板参数时,不会去解析依赖类型的嵌套成员反推模板参数值。哪怕语法全对,只要调用时不手动显式指定invoke<decltype(&BaseClass::print2)>(...),编译器根本不知道Func该匹配成什么类型,直接报模板匹配失败。
修正后的实现不需要为每种参数数量写单独代码
核心调整两个点:
- 把成员函数指针的类型作为独立的、可直接推导的模板参数,不要把它藏在嵌套依赖类型里
- 从成员函数指针的偏特化结构里直接萃取类类型、参数类型,形参直接对接萃取结果,自然触发隐式转换逻辑
下面是可以直接跑的实现,完全匹配你的调用需求:
#include <iostream> #include <type_traits> #include <utility> // 成员函数指针萃取结构:匹配普通无修饰的成员函数 template <auto Func> struct MemFuncTraits; // 偏特化:拆出所属类、返回值、参数列表 template <class Class, typename Ret, typename... Args> struct MemFuncTraits<Ret (Class::*)(Args...)> { using TargetClass = Class; using RetType = Ret; static Ret call(TargetClass* obj, Args... params) { return (obj->*Func)(params...); } }; // 通用invoke实现 template <auto Func, class ObjType, typename... CallArgs> auto invoke(ObjType&& obj, CallArgs&&... args) { using Traits = MemFuncTraits<Func>; // 静态检查:对象类型必须和成员函数所属类兼容 static_assert( std::is_base_of_v<typename Traits::TargetClass, std::decay_t<ObjType>>, "传入对象和成员函数所属类不兼容" ); // 赋值给基类指针时自动触发子类向基类的隐式向上转型 typename Traits::TargetClass* base_ptr = &obj; // 参数匹配成员函数要求的类型,自动触发int转float这类隐式转换 return Traits::call(base_ptr, std::forward<CallArgs>(args)...); } // 测试用例 class BaseClass { public: virtual void print2(float arg1, float arg2) = 0; virtual ~BaseClass() = default; }; class SubClass : public BaseClass { public: void print2(float arg1, float arg2) override { std::cout << "调用成功,参数值:" << arg1 << ", " << arg2 << std::endl; } }; int main() { SubClass subclass; // 完全符合你要求的调用形式 invoke<&BaseClass::print2>(subclass, 1, 2); }
实现效果完全匹配你的三个要求
- 传入的子类对象会在赋值给基类指针时自动完成向上转型,不需要手动写强制转换
- 传入的int类型实参,会自动匹配成员函数要求的float形参,触发隐式类型转换
- 只要传入的对象和成员函数所属类不兼容、参数无法完成隐式转换,就会直接触发静态断言报错或者类型不匹配的编译错误,不会等到运行时出问题
如果你不想用非类型模板参数传成员函数指针,也可以把Func作为独立的可推导参数写,调用时连模板参数都不用手动传:
// 调整后的萃取和invoke template <class Func> struct FunctionPointer; template <class Class, typename... Args> struct FunctionPointer<void (Class::*)(Args...)> { using ClassType = Class; }; template <typename Func, class ObjType, typename... CallArgs> void invoke(Func func, ObjType&& obj, CallArgs&&... args) { using Traits = FunctionPointer<Func>; typename Traits::ClassType* base_ptr = &obj; (base_ptr->*func)(std::forward<CallArgs>(args)...); } // 调用时直接写,完全自动推导 invoke(&BaseClass::print2, subclass, 1, 2);
内容的提问来源于stack exchange,提问作者baechlju
相关产品推荐
相关产品推荐

