泛型lambda式是C++14最振奋人心的特性之一——lambda可以在形参规格中使用auto.这个特性的实现十分直截了当:闭包类中的operator()采用模板实现。例如,给定下述lambda式:
auto f = [](auto x) { return func(normalize(x)); };
则闭包类的函数调用运算符如下所示:
class SomeCompilerGeneratedClassName
{
public:
template
auto operator()(T x) const //auto返回值见条款3
{
return func(normalize(x));
}
... //闭包类的其它功能
};
在本例中,lambda式对x实施的唯一动作就是将其转发给normalize,如果normalize区别对待左值和右值,则可说该lambda式撰写的是有问题的,因为,lambda总会传递左值(形参x)给normalize,即使传递给lambda式的实参是个右值。
该lambda式的正确撰写方式是把x完美转发给normalize,这就要求在代码中修改两处。首先,x要改成万能引用;其次,使用std::forward把x转发给normalize.概念上不难理解,这两处的修改都是举手之劳:
auto f = [](auto&& x)
{ return func(normalize(std::forward??>(x)); };
通常情况下,在使用完美转发时,你是在一个接受型别形参T的模板函数中,所以你写std::forward
条款28解释过,如果把左值传递给万能引用形参,则该形参的型别会成为左值引用;而如果传递的是右值,则该形参会成为右值引用。那意味着在我们的lambda式中,我们可以通过探查x的型别,来判断传入的实参是左值还是右值。decltype提供了实现这一点的一种途径。如果传入的是个左值,decltype(x)将会产生左值引用型别;如果传入的是个右值,decltype(x)将会产生右值引用型别。
条款28还解释了,使用std::forward是惯例是:用型别形参为左值引用表明想要返回左值,而用非引用型别时来表明想要返回的右值。再看我们的lambda式,如果x绑定了左值,decltype(x)将产生左值的引用型别。这符合惯例,不过,如果x绑定的是个右值,decltype(x)将会产生右值引用惯例,而非符合惯例的非引用。
但是,再看下条款28中的std::forward的C++14实现:
template
T&& forward(remove_reference_t& param) //在名字空间std中
{
return static_cast(param);
}
如果客户代码欲完美转发Widget型别的左值,按惯例它应该采用Widget型别(即非引用型别)来实例化std::forward,然后std::forward模板会产生如下函数:
Widget&& forward(Widget& param) //T取值Widget时,std::forward的实例化结果
{
return static_cast(param);
}
但是,如果用户代码想要完美转发Widget的同一右值,但是这次没有遵从惯例将T指定为非引用型别,而是将T指定为右值引用,这会导致什么结果?这就是需要思考的问题,T指定为Widget&&将会发生什么事情。在std::forward完成初步实例化并实施了std::remove_reference_t之后,但在引用折叠发生之前,std::forward如下所示:
Widget&& && forward(Widget& param) //T取值Widget&&时,std::forward的实例化结果
{ //(在引用折叠发生之前)
return static_cast(param);
}
然后,应用引用折叠规则,右值引用的右值引用结果是单个右值引用,实例化结果为
Widget&& forward(Widget& param) //T取值Widget时,std::forward的实例化结果
{ //(在引用折叠发生之后)
return static_cast(param);
}
如果你把这个实例化和在T为Widget时调用的std::forward那个实例化两相比较,你会发现它们别无二致。那就意味着,实例化std::forward时,使用一个右值引用型别和使用一个非引用型别,会产生相同效果。
这个结果非常不错,因为如果传递给我们lambda式的形参x是个右值,decltype(x)产生的是右值引用型别。我们之前已经知道了,传递左值给我们的lambda式时,decltype(x)会产生传递给std::forward的符合惯例的型别,而现在我们又知道对于右值而言,虽然说decltype(x)产生的型别并不符合传递给std::forward的型别形参的惯例,但是产生的结果与符合惯例的型别殊途同归。所以无论左值还是右值,把decltype(x)传递给std::forward都能给出想要的结果。是故,我们的完美转发lambda式可以编写如下:
auto f =
[](auto&& param)
{
return func(normalize(std::forward(param)));
};
在此基础上稍加改动,就可以得到可以接受多个形参的完美转发lambda式版本,因为C++14中的lambda能够接受可变长形参:
auto f =
[](auto&&... param)
{
return func(normalize(std::forward(param)...));
};
对auto&&型别的形参使用decltype,以std::forward之。