33 Use decltype on auto&& parameters to std::forward them
C++14 最令人兴奋的功能是泛型 lambda(generic lambda
),参数能够使用 auto
。这个功能的实现很直接,闭包类的 operator()
是一个模板。比如
class SomeCompilerGeneratedClassName
{
public:
template <typename T> // see Item 3 for
auto operator()(T x) const // auto return type
{
return func(normalize(x));
}
// other closure class functionality
};
x
转发给 normalize
。如果 normalize
对左值和右值处理不同,那么这个 lambda 没有实现对,因为总是将左值传递给 normalize
,即使传递给 lambda 的是右值。
正确的方式是将 x
完美转发给 normalize
。需要做两处修改,第一个将 x
声明为通用引用(参见 Item 24),第二个使用 std::forward
将参数传递给 normalize
(参见 Item 25)。
std::forward
的模板参数是什么?也就是上面的 ???
应该怎么写。
一般使用完美转发的情况是在一个模板函数内部,有一个类型参数 T
,那么写作 std::forward<T>
即可。但是在泛型 lambda 中,没有类型 T
可用。闭包类中有一个模板参数 T
,但是 lambda 中又用不了。
Item 28 解释了,如果左值传递给通用引用参数,参数类型是左值引用。如果传递右值,参数是右值引用。这意味在 lambda 中可以通过检查 x
类型来区分两者。decltype
就是做这个事情的(参见 Item 3)。如果传递的是左值,decltype(x)
返回的是左值引用,如果传递的是右值,decltype(x)
返回的是右值引用。
Item 28 还解释了,当调用 std::forward
时,左值引用意味着传递左值,非引用意味着传递右值。在 lambda 中,如果 x
绑定的是左值,那么 decltype(x)
返回左值引用,这与之前的要求一致。但是如果 x
绑定的是右值,那么 decltype(x)
返回的右值引用,而不是非引用。
但是再来看一下 Item 28 中提到的 std::forward
实现。
template <typename T> // in namespace
T &&forward(remove_reference_t<T> ¶m) // std
{
return static_cast<T &&>(param);
}
Widget
的右值,使用类型 Widget
实例化 std::forward
,那么 std::forward
模板生成的函数如下。
Widget &&forward(Widget ¶m) // instantiation of
{ // std::forward when
return static_cast<Widget &&>(param); // T is Widget
}
Widget
,但是 T
不是非引用,而是右值引用,即 Widget&&
,那么实例化 std::forward
之后且应用了 std::remove_reference_t
,但是没有引用折叠时,实例化的 std::forward
如下。
Widget && &&forward(Widget ¶m) // instantiation of std::forward
{ // when T is Widget&&
return static_cast<Widget && &&>(param); // (before reference- collapsing)
}
Widget &&forward(Widget ¶m) // instantiation of std::forward
{ // when T is Widget&&
return static_cast<Widget &&>(param); // (after reference- collapsing)
}
T
设置为 Widget
的 std::forward
实例化结果可以看出,使用右值引用和非引用实例化 std::forward
的结果是一样的。
这是一个好消息,传递右值,decltype(x)
返回右值引用,将其作为 std::forward
的模板参数,结果与使用非引用结果一致。前面已经论证,传递左值 decltype(x)
返回左值引用是满足需要的。那么将 decltype(x)
作为 std::forward
的模板参数是我们想要的结果。那么完美转发的 lambda 如下。
auto f =
[](auto &&...params)
{
return func(normalize(std::forward<decltype(params)>(params)...));
};
Things to Remember
- Use
decltype
onauto&&
parameters tostd::forward
them.