C++ 类型推导(第三部分)
C++ 类型推导的前两部分都有提到万能引用形式的模板参数类型推导,但是因为它涉及“万能引用”、“引用折叠”等概念,所以当时先略过,现在储备了这些知识也算是完万事具备了,所以现在探讨这种形式的类型推导。
在函数模板中,若函数参数的形式为万能引用,则在模板参数类型的推导结果中会添加实参是左值还是右值的信息。这种信息添加规则决定模板参数的推导结果:如果传入的实参是左值,T的推导结果为左值引用类型,如果传入的实参是右值,T的推导结果为非引用类型。T&& 在叫法上为“万能引用”,但模板实例化结束后万能引用一定是左值引用或右值引用。
C++ 中不允许定义显示定义“引用的引用”,但是在模板实例化过程中、auto 型别生成过程中、创建和运用 typedef (别名声明)、decltype 类型计算过程中出现“引用的引用”是允许的。编译器会使用引用折叠规则生成最终的函数签名(传统的函数参数形式)。引用折叠规则是这样的:如果任一引用为左值引用,则结果为左值引用。只有两个都是右值引用,结果才是右值引用。
如下通过函数模板(foo)的调用示例来揭示万能引用和模板函数调用的运作原理:
- 第10行所示用左值 i 调用了模板函数 foo(T&&),所以 T 的推导结果为 int&;接着得到 foo 的 实例 foo<int&>;然后把出现T的位置都替换为 int&,得到 void foo(int& &&);最后触发引用折叠,产生实际要调用的函数void foo(int&)。
- 第11行所示用右值 18 调用了模板函数 foo(T&&),所以 T 的推导结果为 int;着得到 foo 的 实例 foo<int>;然后把出现T的位置都替换为 int,得到 void foo(int&&);
- 同样 auto&& 的使用存在类型推导的话也会发生类似上述的过程,这里就不再赘述。
可以定义一个没有重载 << 运算符的 类 A 并用该类对象调用模板foo(),来验证上述分析过程。通过报错信息得知编译过程与上述分析基本一致。
以上这些原理、规则是 std::forward 得以正常运行的基础。也就是说当传递给 std::forward 左值时返回一个左值引用,根据定义左值引用是左值;当传递给 std::forward 右值时返回一个右值引用,函数返回的右值是定义为右值的。