为什么需要完美转发
template<typename T>
void g(T x) {
// do something
f(x);
}
int main() {
int a = 42;
g(std::move(a));
g(a);
}
两种不同的 g 函数调用中 f 调用所选择的重载版本都会是左值引用的版本。 实际使用中这种情况非常常见,在 g 函数中检查参数是否正确然后传递给下一个函数调用。
类型推导
template<typename T> void f(T &p);
当一个函数参数是模板类型的普通右值引用时(T&),规定只可以传递一个左值,实参可以是 const 类型。如果实参是 const 类型,那么 T 被推论为 const 类型。
int i = 1;
f(i); // T 被推论为 int
const int ci = 1;
f(ci); // T 被推论为 const int
f(1); // error 传递给 T& 的必须是个左值
template<typename T> void f(const T &p);
,由于右值可以绑定 const 左值引用,所以 const T & 可以接受左值。
int i = 1;
f(i); // T 被推论为 int
const int ci = 1;
f(ci); // T 被推论为 int
f(1); // T 被推论为 int
右值引用函数参数类型推导
template<typename T> void f(T &&p);
正常的绑定规则告诉我们应该传递右值f(1);
此时类似于左值函数参数推导,模板参数 T 为 int 。
但是f(i);
也是被允许的,即使 i 是应该左值,C++ 在正常规则外定义了两个例外规则,允许了这个绑定,完成了类型推导,使得含有 T&& 模板参数的函数在定义中保留了参数的类型信息。
- 当 f 接受实参是左值是,T 被推论为左值引用类型(int&),f 实例化为
**void f<int&>(int &)**
- 当 f 接受实参是右值是,T 被推论为左值类型 (int),f 实例化为
**void f<int>(int &&)**
由于 T&& 接受了两种类型的参数,并且内部做出区分等于我们使用模板参数 T 保留了实参的类型信息。所以我们可以使用 T 来进一步处理类型信息(std::foward)。
引用折叠
为了解释上述的逻辑,还引入了引用折叠的观点来解释,引用折叠只会在模板类型参数间接定义,不可以直接使用。( f(T&&) 接受实参是左值时,T&& T 为 T& 整体才为左值)
TR R
T& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T)
T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T
T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
------------------------------------------------------------------------------
template <typename T>
void deduce(T&& x);
int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)
为什么引入右值引用函数参数类型推导?
假如我们去掉可以 T&& 接受左值的这个特性,然后去实现 g 和 f 的等价调用。
最简单的方法是使用一个 lvalue-reference:
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
E(a, b, c);
}
f(1, 2, 3); 会报错,因为 A& 无法处理右值
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
E(a, b, c); // 假如 E 不是 int& 那么也会报错
}
最终解决方案就是在g和f中处理所有情况
template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);
template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);
template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);
template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);
std::foward
一旦进入函数内部,参数就可以作为左值传递给任何对象
void foo(int&);
template <typename T>
void deduce(T&& x)
{
foo(x); // fine, foo can refer to x
}
deduce(1); // okay, foo operates on x which has a value of 1
利用引用折叠我们可以使用static_cast<T&&>(x);
上面说到 T&& 会保留类型参数,左值-int&,右值-int,再经过 static_cast<T&&>(x);
左值-int&,右值-int&&。
std::forward<A>(a);
// is the same as
static_cast<A&&>(a);