• 引用折叠、万能引用和完美转发那些事



    三者的关系

    我的理解是这样的:

    1. 因为【引用折叠】特性,才有了万能引用。
    2. 【完美转发】的特性是借助【万能引用】以及【forward模板函数】来实现。

    引用折叠

    前面文章 介绍过,什么是引用折叠。总结下来就是C++中的两条规则:

    1. 规则一: 当我们将一个左值传给模板函数的右值引用参数(T&&)时, 编译器推断模板类型参数T为的左值引用类型,例如对于int类型时,推断T为int&.
    2. 例外规则二:如果我们间接创建了一个引用的引用,则这些引用形成了引用折叠。正常情况下,不能直接创建引用的引用,但是可以间接创建。大部分情况下,引用的引用会折叠为普通的左值引用(T& &、T& &&、 T&& &),右值引用的右值引用,则折叠成右值引用。

    举例如下:代码中的函数模板在进行实参推志过程中,T被推导为 int& 类型, int& && 发生引用折叠,最终还是int& 类型。

    
    template<typename T>
    void Print(T&& t) {
    }
    
    int main()
    {
      int a = 10;
      Print(a);
      return 0;
    }
    

    代码中,编译器实例化的结果为:

    
    template<>
    void Print<int &>(int & t)
    {
    }
    

    万能引用

    一句话说,就是:即可以绑定到左值引用也可以绑定到右值引用, 并且还会保持左右值的const属性的函数模板参数。形如这样的参数 T&& 就是万能引用。
    看下面代码的例子,一眼了然:

    template <typename T>
    void MyFunc(T&& value) {
    }
    
    void main() {
    	int a = 10;
    	const int b = 100;
    	MyFunc(a);		// T 为int&  发生引用折叠:int& && ----> int&
    	MyFunc(b);		// T 为const int&   发生引用折叠:constt int& && -----> const int&
    	MyFunc(100);	// T 为int,不发生引用折叠
    	MyFunc(static_cast<const int&&>(100);	// T 为 const int,不发生引用折叠
    }
    
    

    实际上,代码中四次函数模板调用实例化的模板函数分别如下所示:

    template<>
    void MyFunc<int &>(int & value) {
    }
    
    template<>
    void MyFunc<const int &>(const int & value) {
    }
    
    template<>
    void MyFunc<int>(int && value) {
    }
    
    template<>
    void MyFunc<const int>(const int && value) {
    }
    

    完美转发

    为什么需要完美转发

    本质原因是:右值引用的变量在直接用于作表达式时,被认为是左值变量。 (见此处示意代码中有说明)

    举个最简单的例子,下面的代码直接编译报错:

    void Func(int&& a) {
    }
    
    int main() {
        int&& a = 10;
        Func(a);
        return 0;
    }
    

    报错如下:

    yin@yin:~$ g++ 1.cpp
    1.cpp: In function ‘int main()’:
    1.cpp:6:10: error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
         Func(a);
              ^
    1.cpp:1:6: note:   initializing argument 1 of ‘void Func(int&&)’
     void Func(int&& a) {
    
    

    这会一来会导致什么问题呢,那就是函数模板里调用另一个函数模板时,最外层的的函数模板的参数通常都是万能引用(右值引用,T&&), 传递给最外层的函数模板明明一个右值,然而外层函数模板把参数传递给内层的函数模板时,参数却变成了一个左值, 原参数的属性直接丢失了。看下面的举个例子:

    #include <type_traits>
    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    void Func2(T&& j) {
        cout << is_rvalue_reference<T&&>::value << endl;
    }
    
    template <typename T>
    void Func1(T&& i) {
        cout << is_rvalue_reference<T&&>::value << endl;
        Func2(i);
    }
    
    int main() {
        Func1(10);
        return 0;
    }
    

    输出为如下所示:

    yin@yin:~$ ./a.out 
    1
    0
    

    如何解决

    借助引用折叠与万能引用的特性,c++11 标准中提供了一个std::forward<T>()的函数,实现了完美转发。 看看如何使用,以及使用效果:

    #include <type_traits>
    #include <iostream>
    
    using namespace std;
    
    template <typename T>
    void Func2(T&& j) {
        cout << is_rvalue_reference<T&&>::value << endl;
    }
    
    template <typename T>
    void Func1(T&& i) {
        cout << is_rvalue_reference<T&&>::value << endl;
        Func2(std::forward<T>(i));		// 注意,此处使用了std::foward<T>();
    }
    
    int main() {
        Func1(10);
        return 0;
    }
    
    

    输出如下, 符合预期,实现完美转发。

    yin@yin:~$ ./a.out 
    1
    1
    

    内部实现

    想在弄明白原理, 需要结合外层的函数调用(万能引用参数T&&),以及std::forward的内部实现一起来看。

    不太多解释,自己看应该明白。 说几点:

    1. std::remove_reference, 是一个类模板,用于移除类型的引用。具体原型,见后面。
    2. 这里的std::forward的实现使用了两个重载的函数模板。

    std::forward的实现如下(gcc的libstdc++的实现,位于/usr/include/c++/8/bits/move.h文件内):

      /**
       *  @brief  Forward an lvalue.
       *  @return The parameter cast to the specified type.
       *
       *  This function is used to implement "perfect forwarding".
       */
      template<typename _Tp>
        constexpr _Tp&&
        forward(typename std::remove_reference<_Tp>::type& __t) noexcept
        { return static_cast<_Tp&&>(__t); }
    
      /** 
       *  @brief  Forward an rvalue.
       *  @return The parameter cast to the specified type.
       *
       *  This function is used to implement "perfect forwarding".
       */
      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);
        }
    
    

    其它常用到的模板实现

    std::move

    位于/usr/include/c++/8/bits/move.h文件内。

      /**
       *  @brief  Convert a value to an rvalue.
       *  @param  __t  A thing of arbitrary type.
       *  @return The parameter cast to an rvalue-reference to allow moving it.
      */
      template<typename _Tp>
        constexpr typename std::remove_reference<_Tp>::type&&
        move(_Tp&& __t) noexcept
        { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
    
    

    remove_reference

    位于/usr/include/c++/8/type_traits文件内。

    /// remove_reference
      template<typename _Tp> 
        struct remove_reference
        { typedef _Tp   type; };
    
      template<typename _Tp> 
        struct remove_reference<_Tp&>
        { typedef _Tp   type; };
    
      template<typename _Tp> 
        struct remove_reference<_Tp&&>
        { typedef _Tp   type; };
    
    

    参考

    1. gcc libstdc++的库
    2. cppinsight
  • 相关阅读:
    Lexical Sign Sequence
    (UPCOJ暑期训练)Tally Counters
    (2019hdu多校第十场) Welcome Party
    (2019hdu多校第十场1003) Valentine's Day
    更新,线段树模板(支持相关基本操作)
    linux(deepin)下Clion的安装及环境配置
    2019牛客第7场——C(Governing sand)
    【数论】数论之旅:N!分解素因子及若干问题
    [二分]Kayaking Trip
    [数论之旅]数学定理
  • 原文地址:https://www.cnblogs.com/yinheyi/p/14853787.html
Copyright © 2020-2023  润新知