引用折疊、萬能引用和完美轉發那些事



三者的關系

我的理解是這樣的:

  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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM