类模板参数包完美转发失效,求无需std::move的可重写call方案
问题根因解释
你遇到的编译错误核心原因是:Params是类层级的模板参数,并非call函数自身的模板参数,因此Params&&不属于转发引用,只是普通的右值引用,只能绑定右值,无法绑定左值,所以传入左值变量i时编译器会报错。而call1的TParams是函数自身的模板参数,符合转发引用的语法规则,所以可以正常接收左值和右值参数。
模板函数无法被重写的限制,和你需要完美转发的需求并不冲突,可通过NVI(非虚接口)模式完美解决,不需要用户侧显式调用std::move,也不会产生额外的栈拷贝开销。
解决方案:NVI模式实现可重写的完美转发接口
核心设计思路如下:
- 对外暴露public模板函数
call,使用函数级别的模板参数实现转发引用,自动适配所有参数的值类别,用户调用无需做任何额外处理 - 内部提供protected非模板虚函数
call_impl,参数类型使用类层级的模板参数包,支持子类继承重写 - 对外的
call接口将参数完美转发给内部的call_impl执行实际逻辑
继承场景示例代码
#include <tuple> #include <utility> template<typename Ret, typename... Params> class Base { public: // 对外公开的模板接口,支持完美转发 template<typename... Args> Ret call(Args&&... args) { static_assert(sizeof...(Args) == sizeof...(Params), "参数数量不匹配"); return call_impl(std::forward<Args>(args)...); } protected: // 内部可重写的虚实现,const左值引用兼容所有值类别 virtual Ret call_impl(const Params&... data) = 0; }; template<typename Ret, typename... Params> class Derived : public Base<Ret, Params...> { protected: Ret call_impl(const Params&... data) override { // 转发到父类实现或自定义逻辑,需要转移所有权时手动加std::move即可 return Base<Ret, Params...>::call_impl(data...); } }; int main() { Derived<void, int, short> MyDerived; int i = 32; // 直接传参即可,无需std::move,左值右值都支持 MyDerived.call(i, 0); // 需要转移所有权时再显式调用std::move MyDerived.call(std::move(i), 0); }
组合场景适配示例
#include <tuple> #include <utility> template<typename Ret, typename... Params> class Composit { public: template<typename... Args> Ret call(Args&&... args) { static_assert(sizeof...(Args) == sizeof...(Params), "参数数量不匹配"); return call_impl(std::forward<Args>(args)...); } protected: virtual Ret call_impl(const Params&... data) = 0; }; template<typename Ret, typename... Params> class MainClass { public: MainClass() : callee(new Composit<Ret, Params...>()) {} // 对外接口同样用完美转发 template<typename... Args> Ret call(Args&&... args) { return callee->call(std::forward<Args>(args)...); } private: Composit<Ret, Params...>* callee; }; int main() { MainClass<void, int, short> MyClass; int i = 32; // 正常传参即可 MyClass.call(i, 0); }
方案优势
- 完全满足可继承重写的需求,内部实现为非模板虚函数,支持正常override
- 用户侧调用无任何额外负担,不需要显式写
std::move,自动兼容左值、右值参数 - 无额外拷贝开销,参数全程通过引用传递,不会产生多余的栈内存分配
- 兼容C++11及以上标准,无更高版本依赖
内容的提问来源于stack exchange,提问作者NetoBF
相关产品推荐
相关产品推荐

