Effective Modern C++(三)引用折叠
template<typename T>
void func(T&& param);
对于一个通用引用,只有当实参被用来实例化通用引用形参时,才会推导形参T。
编码机制是简单的。当左值实参被传入时,T
被推导为左值引用。当右值被传入时,T
被推导为非引用。
Widget widgetFactory(); //返回右值的函数 Widget w; //一个变量(左值) func(w); //用左值调用func;T被推导为Widget& func(widgetFactory()); //用右值调用func;T被推导为Widget
这一编码机制也是forward函数进行完美转发的基础。
在此,我们需要认识到,声明一个引用的引用是不合法的,因为引用本身不是一个对象,所以不能定义引用的引用
int x; … auto& & rx = x; //错误!不能声明引用的引用
但是,再次考虑这个模版,当我们的T是一个左值时,模版会被实例为一下样式
template<typename T> void func(T&& param); func(w); void func(Widget& && param);
一个引用的引用,这显然是不合法的,但是编译器为什么没有报错呢?
答案是引用折叠(reference collapsing)。是的,禁止你声明引用的引用,但是编译器会在特定的上下文中产生这些,模板实例化就是其中一种情况。当编译器生成引用的引用时,引用折叠指导下一步发生什么。
引用根据规则折叠为单个引用:
如果任一引用为左值引用,则结果为左值引用。否则(即,如果引用都是右值引用),结果为右值引用。
再次回看上节讲的forward函数
template<typename T> void f(T&& fParam) { … //做些工作 someFunc(std::forward<T>(fParam)); //转发fParam到someFunc }
因为fParam
是通用引用,我们知道类型参数T
的类型根据f
被传入实参(即用来实例化fParam
的表达式)是左值还是右值来编码。
std::forward
的作用是当且仅当传给f
的实参为右值时,即T
为非引用类型,才将fParam
(左值)转化为一个右值。
std::forward
可以这样实现:
template<typename T> //在std命名空间 T&& forward(typename remove_reference<T>::type& param) { return static_cast<T&&>(param); }
当实参为一个左值时,模版被实例化为
Widget& forward(Widget& param) { return static_cast<Widget&>(param); }
当实参为一个右值时,T被推导为一个非引用,因此模版被实例化为
Widget&& forward(Widget& param) { return static_cast<Widget&&>(param); }
引用折叠发生在四种情况下。第一,也是最常见的就是模板实例化。第二,是auto
变量的类型生成,具体细节类似于模板,因为auto
变量的类型推导基本与模板类型推导雷同。
以及typedef
与别名声明的创建和使用,decltype
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通