Parameter pack与完美转发代码编译失败问题求助
问题分析
编译错误的核心原因是:Obj类的模板构造函数会拦截拷贝构造的调用。
当你在main中把obj传入run函数,最终会进入print(T param, Args... args)——这里T param是传值传递,所以需要拷贝构造一个Obj对象。此时编译器会在重载候选中选择最匹配的构造函数:
- 默认生成的拷贝构造函数是
Obj(const Obj&) - 模板构造函数会被实例化为
Obj(Obj&)(因为传入的是非const左值obj)
由于Obj(Obj&)比Obj(const Obj&)更匹配非const左值,编译器会选择这个模板构造函数。但这个构造函数试图用Obj类型的参数去初始化std::string name,而std::string没有接受Obj的构造函数,因此触发编译错误。
解决方案
提供几种可行的修复方式:
方式一:显式定义拷贝/移动构造函数
显式声明并定义拷贝、移动构造函数——非模板的重载在解析时优先级高于模板版本,编译器会正确调用拷贝/移动逻辑:
class Obj{ public: std::string name = "Name"; std::string l_name = "LastName"; template<typename P> Obj(P&& param): name{std::forward<P>(param)} { } // 显式拷贝构造函数 Obj(const Obj& other) : name(other.name), l_name(other.l_name) {} // 显式移动构造函数,避免模板构造函数拦截移动操作 Obj(Obj&& other) noexcept : name(std::move(other.name)), l_name(std::move(other.l_name)) {} friend std::ostream& operator<<(std::ostream& os, const Obj& obj); };
方式二:给模板构造函数添加SFINAE约束
让模板构造函数只接受能用来初始化std::string的类型,排除Obj自身的类型:
#include <type_traits> // 需要包含该头文件 class Obj{ public: std::string name = "Name"; std::string l_name = "LastName"; // 约束:P不是Obj的同类型或引用类型 template<typename P, typename = std::enable_if_t<!std::is_same_v<std::decay_t<P>, Obj>>> Obj(P&& param): name{std::forward<P>(param)} { } friend std::ostream& operator<<(std::ostream& os, const Obj& obj); };
方式三:修改print函数为传const引用
避免不必要的拷贝,同时绕过拷贝构造的问题(因为不需要拷贝Obj对象),这种方式更高效:
template<typename T, typename ...Args> void print(const T& param, Args... args) { // 改为const T&传参 std::size_t count = sizeof...(args); std::cout << param; if ( count != 0 ) { std::cout << ","; } print(args...); }
修复后的完整代码示例
这里结合方式二和方式三,兼顾构造函数正确性与运行效率:
#include <iostream> #include <string> #include <type_traits> class Obj{ public: std::string name = "Name"; std::string l_name = "LastName"; template<typename P, typename = std::enable_if_t<!std::is_same_v<std::decay_t<P>, Obj>>> Obj(P&& param): name{std::forward<P>(param)} { } Obj(const Obj& other) : name(other.name), l_name(other.l_name) {} Obj(Obj&& other) noexcept : name(std::move(other.name)), l_name(std::move(other.l_name)) {} friend std::ostream& operator<<(std::ostream& os, const Obj& obj); }; std::ostream& operator<<(std::ostream& os, const Obj& obj) { os << obj.name << ":" << obj.l_name; return os; } void print() { std::cout << "}"; } template<typename T, typename ...Args> void print(const T& param, Args... args) { std::size_t count = sizeof...(args); std::cout << param; if ( count != 0 ) { std::cout << ","; } print(args...); } template<typename... Args> void run(Args... args) { std::cout << "{"; print(args...); } int main() { Obj obj{"obj"}; run("1", "2", 1.3, std::string{"Some Message"}, obj); return 0; }
内容的提问来源于stack exchange,提问作者Qwe Qwe
相关产品推荐
相关产品推荐

