关于C++完美转发中模板实参推导结果的疑问
我正在学习C++完美转发,运行了如下示例代码:
template<typename T> void show_type(T t){ std::cout << typeid(t).name() << std::endl; } template<typename T> void perfect_forwarding(T &&t){ std::cout << std::boolalpha << "is std::string: " << std::is_same<std::string, T>::value << std::endl; std::cout << std::boolalpha << "is lval-reference: " << std::is_lvalue_reference<T>::value << std::endl; std::cout << std::boolalpha << "is rval-reference: " << std::is_rvalue_reference<T>::value << std::endl; show_type(static_cast<T&&>(t)); } std::string get_string(){ return "hi world"; } int main() { std::string s = "hello world"; perfect_forwarding(s); //call 1 perfect_forwarding(get_string()); //call 2 }
程序输出:
is std::string: false is lval-reference: true is rval-reference: false Ss is std::string: true is lval-reference: false is rval-reference: false Ss
通过GDB调试发现,两次perfect_forwarding调用的实例化结果为:
perfect_forwarding<std::string&>(std::string&) //for call 1 perfect_forwarding<std::string>(std::string&&) //for call 2
我的疑问是:
- 在
call 1中,为何T被推导为std::string&?我原本以为T会是std::string,且T&&会被推导为std::string&&; - 在
call 2中,我原本以为T会被推导为std::string&&,实际却并非如此,希望得到解答。
核心知识点:转发引用与引用折叠规则
首先明确:模板中的T&&不是普通右值引用,而是转发引用(Forwarding Reference),它的推导逻辑依赖C++的引用折叠规则:
T& &→T&T& &&→T&T&& &→T&T&& &&→T&&
转发引用的推导规则是:
- 传递左值给
T&&参数时,模板参数T推导为左值引用类型; - 传递右值给
T&&参数时,模板参数T推导为非引用类型。
对Call 1的解释:传递左值s
main中的s是std::string类型的左值,调用perfect_forwarding(s)时:
- 模板参数
T被推导为std::string&(左值引用); - 函数参数
T&&展开为std::string& &&,根据引用折叠规则最终变为std::string&(左值引用),和GDB显示的实例化结果一致。
你原本以为T会是std::string,但如果真的这样,T&&就是std::string&&(右值引用),而右值引用无法绑定左值,这是非法的。编译器必须推导T为左值引用,才能让参数最终成为左值引用,合法绑定左值s。
对Call 2的解释:传递右值get_string()
get_string()返回的是std::string临时对象(右值),调用perfect_forwarding(get_string())时:
- 模板参数
T被推导为std::string(非引用类型); - 函数参数
T&&直接是std::string&&(右值引用),可以合法绑定右值,对应GDB的实例化结果。
你原本以为T会是std::string&&,但如果真的这样,T&&会展开为std::string&& &&,折叠后还是std::string&&,虽然最终参数类型相同,但这不符合转发引用的设计逻辑——推导T为非引用类型,是为了配合后续static_cast<T&&>(t)实现完美转发:当T是左值引用时,static_cast<T&&>(t)保留左值属性;当T是非引用类型时,static_cast<T&&>(t)将t转换为右值,从而完整转发原始值的类别属性。
补充:为什么show_type输出都是Ss?
show_type的参数是值传递,不管传入左值还是右值,都会触发拷贝(或移动),最终参数t的类型都是std::string,所以typeid(t).name()的结果都是Ss(GCC中std::string的类型名缩写)。
内容的提问来源于stack exchange,提问作者Searcher

