条款33:对auto&&型别的形参使用decltype,以std::forward之

泛型lambda式是C++14最振奋人心的特性之一——lambda可以在形参规格中使用auto.这个特性的实现十分直截了当:闭包类中的operator()采用模板实现。例如,给定下述lambda式:

auto f = [](auto x) { return func(normalize(x)); };

则闭包类的函数调用运算符如下所示:

class SomeCompilerGeneratedClassName
{
public:
    template<typename T>
    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<T>就好。而在泛型lambda式中,却没有可用的型别形参T。在lambda式生成的闭包内的模板化operator()函数中的确有个T,但是在Lambda中无法指涉之,所以有也没用。

条款28解释过,如果把左值传递给万能引用形参,则该形参的型别会成为左值引用;而如果传递的是右值,则该形参会成为右值引用。那意味着在我们的lambda式中,我们可以通过探查x的型别,来判断传入的实参是左值还是右值。decltype提供了实现这一点的一种途径。如果传入的是个左值,decltype(x)将会产生左值引用型别;如果传入的是个右值,decltype(x)将会产生右值引用型别。

条款28还解释了,使用std::forward是惯例是:用型别形参为左值引用表明想要返回左值,而用非引用型别时来表明想要返回的右值。再看我们的lambda式,如果x绑定了左值,decltype(x)将产生左值的引用型别。这符合惯例,不过,如果x绑定的是个右值,decltype(x)将会产生右值引用惯例,而非符合惯例的非引用。

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

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

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

Widget&& forward(Widget& param)    //T取值Widget时,std::forward的实例化结果
{
    return static_cast<Widget&&>(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<Widget&& &&>(param);   
}

然后,应用引用折叠规则,右值引用的右值引用结果是单个右值引用,实例化结果为

Widget&& forward(Widget& param)    //T取值Widget时,std::forward的实例化结果
{                                  //(在引用折叠发生之后)
    return static_cast<Widget&&>(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<decltype(param)>(param)));
    };

在此基础上稍加改动,就可以得到可以接受多个形参的完美转发lambda式版本,因为C++14中的lambda能够接受可变长形参:

auto f = 
    [](auto&&... param)
    {
        return func(normalize(std::forward<decltype(param)>(param)...));
    };
要点速记

对auto&&型别的形参使用decltype,以std::forward之。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值