泛型lambda式(generic lambda)是C++14最振奋人心的特征之一,lambda可以在形参规格中使用auto。这个特性的实现十分直接了当:闭包类中的operator()采用模板实现。例如,给定下述lambda式:
auto f = [](auto x) { return func(normalize(x));};
以上语句翻译的闭包类代码如下:
class SomeComilerGeneratedClassName {
public:
template<typename T> //auto型别的返回值
auto operator()(T x) const //参见Item 3
{ return func(normalize(x));}
...
}; //闭包类的其他功能
在本例中,lambda式对x实施的唯一动作就是将其转发给normalize。但是如果normalize区别对待左值和右值,那么该lambda式撰写的是有问题的,因为在此lambda总会传递左值(形参x)给normalize,即使传递给lambda式的实参是个右值。
该lambda式的正确撰写方式是把x完美转发给normalize,这就要求代码中修改两处,首先,x要改成万能引用(参见Item 24);其次,使用std::forward(参见Item 25)把x转发给normalize。概念上不难理解,这两处的修改都是举手之劳:
auto f = [](auto&& x){ return func(normalize(std::forward<???>(x)));};
可是问题就出在,上面代码里面的???
应该怎么办呢?
通常情况下,在使用完美转发的时候,你是在一个接受型别形参T的模板函数中,所以,你写std::forward<T>就好。但是在泛型lambda式中,却没有可用的型别形参T。在lambda式生成的闭包内的模板化operator()函数中的确有个T,但是在lambda式中无法指涉,所以也没用。
前面我们已经说过,如果把实参左值传递给万能引用型别的形参,则该形参的型别会被推导为左值引用,如果传递是右值,则该形参会成为右值引用。那这也就意味着我们可以在该lambda表达式中探查x的型别,判断传入的实参是左值还是右值。而decltype则可以完成探查工作,如果传入的是个左值,decltype(x)将会产生左值引用型别,如果传入的是个右值,decltype(x)将会产生右值引用。
我们还知道,使用std::forward时惯例是:用型别形参为左值引用表明想要返回左值,而用非引用型别时来表达想要返回的右值。
再看看我们的lambda式,如果x绑定了左值,decltype(x)将产生左值引用型别。这符合惯例。不过,如果x绑定的是个右值,decltype(x)将会产生右值引用惯例,并非惯例的右值。
但是这里需要说明的是,在使用std::forward
时,使用一个右值引用型别和使用一个非引用型别,会产生相同结果。所以如果x绑定的是个右值,decltype(x)后的std::forward()
依然符合惯例。
示例代码如下:
//单一变量
auto f =
[](auto&& param)
{
return func(normalizer(std::forward<decltype(param)>(param)));
}
//多变量
auto f =
[](auto&&... param)
{
return func(normalizer(std::forward<decltype(param)>(param)...));
}