模板參數的“右值引用”是轉發引用

来源:https://www.cnblogs.com/jerry-fuyi/archive/2020/04/19/12733924.html
-Advertisement-
Play Games

在C++11中, 不再只有邏輯與的含義,還可能是右值引用: 但也不盡然, 還可能是轉發引用: “轉發引用”(forwarding reference)舊稱“通用引用”(universal reference),它的“通用”之處在於你可以拿一個左值綁定給轉發引用,但不能給右值引用: 一個函數的參數要想 ...


在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上撞撞牆才能學會一點東西。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在 JavaScript 中,new 運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。創建一個對象很簡單,為什麼我們還要多此一舉使用 new 運算符呢?它到底有什麼樣的魔力? ...
  • 判斷input上傳文件類型,文件大小。input獲取的size為位元組,判斷大小的時候需要把限制大小轉換為位元組 ...
  • 前情提要: 現有一個網站框架,包括主體項目WebApp一個,包含 IIdentityUser 介面的基架項目 A。用於處理用戶身份驗證的服務 AuthenticationService 位於命名空間B。用於保存數據的實體 User : IIdentityUser 位置項目C。項目之間的關係是B和C依 ...
  • 1 簡介 是最簡單的使用 的方式,而 是最流行的 資料庫。兩者在分散式、微服務架構中使用率極高,本文將用實例介紹如何在 中整合 的兩種方法: 和`MongoTemplate`。 代碼結構如下: 2 項目準備 2.1 啟動MongoDB實例 為了方便,使用 來啟動 ,詳細指導文檔請參考: "用Dock ...
  • 知識無界 記錄tomcat和mybatis源碼導入到eclipse的過程 開發這麼久了,不能老crud,看看人家的源碼和設計思路。 0. tomcat源碼導入eclipse 下載tomcat源碼https://github.com/apache/tomcat, 然後按步驟下載(也可用git clon ...
  • springboot 項目使用阿裡雲簡訊服務發送手機驗證碼 (第一篇) 1、註冊阿裡雲賬戶進行賬號實名認證 2、申請簡訊簽名和模板 3、創建access_key和access_secret 4、然後就是代碼編寫 一、找到產品與服務裡面的雲通信模塊,然後找到簡訊服務,開通簡訊服務。我這裡已經開通,可直 ...
  • Python變數和數據類型 數據類型 print語句 註釋 Python的註釋以 # 開頭,後面的文字直到行尾都算註釋 # 這一行全部都是註釋... print 'hello' # 這也是註釋 這裡要註意註意:不管你是為了Python就業還是興趣愛好,記住:項目開發經驗永遠是核心,如果你沒有2020 ...
  • 頁面寫死el-select下拉框標簽: 通過v-for="item in stateArr"綁定,stateArr聲明在Vue組件裡面的data參數裡面代碼如下: <el-form class="small-space" :model="createdItem" label-position="le ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...