Effective Modern C++ Item 33 对auto&&型别的形参使用decltype,以std::forward之

1. 你有完美转发lambda入参的需求么?

泛型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));}
    ...
};                                      //闭包类的其他功能

传送门:Item 3

在本例中,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)));};

可是问题就出在,上面代码里面的???应该怎么办呢?

2. 怎么完美转发lambda函数的入参

通常情况下,在使用完美转发的时候,你是在一个接受型别形参T的模板函数中,所以,你写std::forward<T>就好。但是在泛型lambda式中,却没有可用的型别形参T。在lambda式生成的闭包内的模板化operator()函数中的确有个T,但是在lambda式中无法指涉,所以也么用。

Item 28解释过,如果把左值传递给万能引用的形参,则该形参的型别会作为左值引用,如果传递是右值,则该形参会成为右值引用。那意味着在我们的lambda式中,我们可以通过探查x的型别,来判断传入的实参是左值还是右值。

decltype(x)将会产生左值引用型别,如果传入的是个右值,decltype(x)将会产生右值引用。

Item 28还解释了,使用std::forward时惯例是:用型别形参为左值引用表明想要返回左值,而用非引用型别时来表达想要返回的右值。

再看看我们的lambda式,如果x绑定了左值,decltype(x)将产生左值引用型别。这符合惯例。不过,如果x绑定的是个右值,decltype(x)将会产生右值引用惯例,而非符合惯例的非引用。

3. 模板实例化分析

但是,再看下Item 28中的std::forward的C++14实现:

template<typename T>                            //在名字空间std中
T&& forward(remove_reference_t<T>& param)
{
    return static_cast<T&&>(param);
}

如果客户代码欲完美转发Widget型别的右值,按照惯例,它应该采用Widget型别(即非引用型别)来实例化std::forward,然后std::forward模板会产生如下函数:

Widget&& forward(Widget& param)             //T取Widget时
{
    return static_cast<Widget&&>(param);    //std::forward的实例化结束 
}

但是,如果客户代码想要完美转发Widget的同一右值,但是这次没有遵从惯例将T指定为非引用型别,而是将T指定为右值引用,这会导致什么结果?这就是需要思考的问题,T指定为Widget&&将会发生什么事情。在std::forward完成初步的实例化并实施了std::remove_reference_t之后,但在引用折叠(再参见Item 28)发生之前,std::forward如下所示:

Widget&& && forward(Widget& param)          //T取Widget&&时
{
    return static_cast<Widget&& &&>(param);     //std::forward的实例化结束 
                                                //(在引用折叠发生之前)
}

然后,发生引用折叠,右值引用的右值引用结果是单个右值引用,实例化结果为:

Widget&& forward(Widget& param)          //T取Widget&&时
{
    return static_cast<Widget&&>(param);     //std::forward的实例化结束 
                                             //(在引用折叠发生之后)
}

对比之前,发现一样的。这意味着,实例化std::forward时,使用一个右值引用型别和使用一个非引用型别,会产生相同结果

4. 结论

这个结果非常不错,因为如果传递给lambda式的形参x是个右值,decltype(x)产生的是右值引用型别,我们之前已经知道了,传递左值给我们的lambda式时,decltype(x)会产生传递给std::forward符合惯例的型别,而现在,我们又知道对右值而言,虽然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)...));
    }
要点速记
1. 对auto&&型别的形参使用decltype,以std::forward之。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值