C++类模板BaseMethod实例化报错:无法支持多参数模板形式
问题描述
我实现了一个作为函数包装器的C++类模板BaseMethod,代码如下:
using MethodId = std::uint16_t; template <typename Func> class BaseMethod { public: BaseMethod(const Func &func, std::uint16_t method_id) : _function(func), _method_id(method_id) {} template <typename... Args> auto operator()(Args... args) -> decltype(_function(std::forward<Args>(args)...)) { return _function(std::forward<Args>(args)...); } template <typename... Args> auto invoke(Args... args) -> decltype(_function(std::forward<Args>(args)...)) { return _function(std::forward<Args>(args)...); } private: Func _function; MethodId _method_id; };
目前可通过以下方式实例化:
int adder(int x, int y){return x+y;} BaseMethod<int(*)(int, int)> foo_template(adder, 0);
我希望支持更简洁的实例化方式:
BaseMethod<int, int, int> bar_template(adder, 0);
为此我添加了针对函数指针的偏特化版本:
template <typename Ret, typename... Args> class BaseMethod<Ret (*)(Args...)> { public: BaseMethod(Ret (*func)(Args...), std::uint16_t method_id) : _function(func), _method_id(method_id) { } Ret operator()(Args... args) { return _function(std::forward<Args>(args)...); } Ret invoke(Args... args) { return _function(std::forward<Args>(args)...); } private: Ret (*_function)(Args...); std::uint16_t _method_id; };
但当尝试使用BaseMethod<int, int, int> bar_template(adder, 0);时,IDE报错:too many arguments for class template "BaseMethod",请问我的代码存在什么问题?
问题分析
报错的核心原因很明确:
- 你的主模板
BaseMethod仅定义了1个模板参数Func,而BaseMethod<int, int, int>一次性传入了3个模板参数,编译器找不到匹配的模板定义,因此抛出参数过多的错误。 - 你写的偏特化版本
BaseMethod<Ret (*)(Args...)>,是针对「模板参数为函数指针类型」的场景(比如BaseMethod<int(*)(int,int)>),并非直接支持传入多个独立的返回值、参数类型作为模板参数。
解决方案
要实现BaseMethod<Ret, Arg1, Arg2,...>的简洁实例化方式,有三种常见可行思路:
思路1:重构主模板为多参数版本
直接将主模板改为接受返回值类型和参数类型列表,同时保留对任意可调用对象的支持:
#include <functional> #include <type_traits> using MethodId = std::uint16_t; // 主模板:接受返回值+参数类型 template <typename Ret, typename... Args> class BaseMethod { public: // 针对函数指针的构造函数 BaseMethod(Ret (*func)(Args...), std::uint16_t method_id) : _function(func), _method_id(method_id) {} // 支持lambda、std::function等其他可调用对象的构造函数 template <typename Func, typename = std::enable_if_t<std::is_invocable_r_v<Ret, Func, Args...>>> BaseMethod(Func&& func, std::uint16_t method_id) : _function(std::forward<Func>(func)), _method_id(method_id) {} Ret operator()(Args... args) { return std::invoke(_function, std::forward<Args>(args)...); } Ret invoke(Args... args) { return std::invoke(_function, std::forward<Args>(args)...); } private: std::function<Ret(Args...)> _function; MethodId _method_id; };
此时你可以直接用BaseMethod<int, int, int> bar_template(adder, 0);实例化,同时兼容lambda等其他可调用对象。
思路2:保留原模板,新增模板别名
如果不想修改原有主模板和偏特化代码,可以新增一个模板别名,将Ret, Args...转换为对应的函数指针类型:
// 原主模板和偏特化代码保留不变 // 新增模板别名,简化写法 template <typename Ret, typename... Args> using BaseMethodFunc = BaseMethod<Ret(*)(Args...)>;
使用时:
BaseMethodFunc<int, int, int> bar_template(adder, 0);
思路3:利用C++17类模板推导
如果你使用C++17或更高版本,甚至不需要手动指定模板参数,编译器会自动推导类型:
// 保留原主模板和偏特化代码 BaseMethod bar_template(adder, 0); // 编译器自动推导Func为int(*)(int,int)
内容的提问来源于stack exchange,提问作者Xin_Wang
相关产品推荐
相关产品推荐

