zoukankan      html  css  js  c++  java
  • 模板参数的“右值引用”是转发引用

    在C++11中,&&不再只有逻辑与的含义,还可能是右值引用:

    void f(int&& i);
    

    但也不尽然,&&还可能是转发引用:

    template<typename T>
    void g(T&& obj);
    

    “转发引用”(forwarding reference)旧称“通用引用”(universal reference),它的“通用”之处在于你可以拿一个左值绑定给转发引用,但不能给右值引用:

    void f(int&& i) { }
    
    template<typename T>
    void g(T&& obj) { }
    
    int main()
    {
        int n = 2;
        f(1);
    //  f(n); // error
        g(1);
        g(n);
    }
    

    一个函数的参数要想成为转发引用,必须满足:

    • 参数类型为T&&,没有constvolatile

    • T必须是该函数的模板参数。

    换言之,以下函数的参数都不是转发引用:

    template<typename T>
    void f(const T&&);
    template<typename T>
    void g(typename std::remove_reference<T>&&);
    template<typename T>
    class A
    {
        template<typename U>
        void h(T&&, const U&);
    };
    

    另一种情况是auto&&变量也可以成为转发引用:

    auto&& vec = foo();
    

    所以写范围for循环的最好方法是用auto&&

    std::vector<int> vec;
    for (auto&& i : vec)
    {
        // ...
    }
    

    有一个例外,当auto&&右边是初始化列表,如auto&& l = {1, 2, 3};时,该变量为std::initializer_list<int>&&类型。

    转发引用,是用来转发的。只有当你的意图是转发参数时,才写转发引用T&&,否则最好把const T&T&&写成重载(如果需要的话还可以写T&,还有不常用的const T&&;其中T是具体类型而非模板参数)。

    转发一个转发引用需要用std::forward,定义在<utility>中:

    #include <utility>
    
    template<typename... Args>
    void f(Args&&... args) { }
    
    template<typename T>
    void g(T&& obj)
    {
        f(std::forward<T>(obj));
    }
    
    template<typename... Args>
    void h(Args&&... args)
    {
        f(std::forward<Args>(args)...);
    }
    

    调用g有几种可能的参数:

    • int i = 1; g(i);Tint&,调用g(int&)

    • const int j = 2; g(j);Tconst int&,调用g(const int&)

    • int k = 3; g(std::move(k));g(4);Tint(不是int&&哦!),调用g(int&&)

    你也许会疑惑,为什么std::move不需要<T>std::forward需要呢?这得从std::forward的签名说起:

    template<typename T>
    constexpr T&& forward(std::remove_reference_t<T>&) noexcept;
    template<typename T>
    constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;
    

    调用std::forward时,编译器无法根据std::remove_reference_t<T>反推出T,从而实例化函数模板,因此<T>需要手动指明。

    但是这并没有从根本上回答问题,或者可以进一步引出新的问题——为什么std::forward的参数不定义成T&&呢?

    原因很简单,T&&会把T&const T&T&&const T&&(以及对应的volatile)都吃掉,有了T&&以后,再写T&也没用。

    且慢,T&&参数在传入函数是会匹配到T&&吗?

    #include <iostream>
    #include <utility>
    
    void foo(int&)
    {
        std::cout << "int&" << std::endl;
    }
    
    void foo(const int&)
    {
        std::cout << "const int&" << std::endl;
    }
    
    void foo(int&&)
    {
        std::cout << "int&&" << std::endl;
    }
    
    void bar(int&& i)
    {
        foo(i);
    }
    
    int main()
    {
        int i;
        bar(std::move(i));
    }
    

    不会!程序输出int&。在函数bar中,i是一个左值,其类型为int的右值引用。更直接一点,它有名字,所以它是左值。

    因此,如果std::forward没有手动指定的模板参数,它将不能区分T&T&&——那将是“糟糕转发”,而不是“完美转发”了。

    最后分析一下std::forward的实现,以下代码来自libstdc++:

    template<typename _Tp>
      constexpr _Tp&&
      forward(typename std::remove_reference<_Tp>::type& __t) noexcept
      { return static_cast<_Tp&&>(__t); }
    
    template<typename _Tp>
      constexpr _Tp&&
      forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
      {
        static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
                      " substituting _Tp is an lvalue reference type");
        return static_cast<_Tp&&>(__t);
      }
    
    • 当转发引用T&& obj绑定左值int&时,匹配第一个重载,_TpTint&,返回类型_Tp&&int&(引用折叠:& && &&&& &都折叠为&,只有&& &&折叠为&&);

    • const int&同理;

    • 当转发引用绑定右值int&&时,匹配第二个重载,_Tpint,返回类型为int&&

    • const int&&同理。

    综上,std::forward能完美转发。

    程序员总是要在Stack Overflow上撞撞墙才能学会一点东西。

  • 相关阅读:
    继续学习AJAX
    最近在看AJAX
    selenium学习模拟键盘按键操作
    二十三。克隆
    二十五。继承
    十八。类的属性
    二十一。第四章综合例题
    二十四。继承
    十七。对JAVA中堆和栈的细致了解
    十六。方法调用以及传参
  • 原文地址:https://www.cnblogs.com/jerry-fuyi/p/12733924.html
Copyright © 2011-2022 走看看