C++里也有菱形運算符?

来源:https://www.cnblogs.com/apocelipes/p/18166068
-Advertisement-
Play Games

最近在翻《c++函數式編程》的時候看到有一小節在說c++14新增了“菱形運算符”。我尋思c++里好像沒什麼運算符叫這名字啊,而且c++14新增的功能很少,我也不記得有添加這種語法特性。一瞬間我有些懷疑我的記憶了,所以為了查漏補缺,我寫了這篇文章。 什麼是菱形運算符 這個概念在Java里比較多見: L ...


最近在翻《c++函數式編程》的時候看到有一小節在說c++14新增了“菱形運算符”。我尋思c++里好像沒什麼運算符叫這名字啊,而且c++14新增的功能很少,我也不記得有添加這種語法特性。一瞬間我有些懷疑我的記憶了,所以為了查漏補缺,我寫了這篇文章。

什麼是菱形運算符

這個概念在Java里比較多見:

List<String> myList = new ArrayList<>();

這東西在Java里的學名是diamond operator,表示使用泛型類並且類型參數在左側的表達式已給出因此在右側可以省略。

簡單的說就是讓你少寫幾次重覆的類型參數。因為看起來像個菱形所以得名菱形運算符。

然後我們偶爾會在c++里看到形狀上很相似的東西:

std::sort(vec.begin(), vec.end(), std::greater<>());

<>出現在模板的特化中是我們所熟悉的,但這個std::greater<>()是什麼呢?

c++沒有菱形運算符

先說結論,從語言標準來說,c++里沒有什麼菱形運算符。

c++20里雖然新增了一個運算符operator<=>,但這個和所謂的菱形運算符沒有任何關係。

那問題來了,std::greater<>()是什麼以及為什麼書里說是c++14新增的特性呢?難道書里瞎說的嗎?但事實是這樣的示例代碼在c++14以及之後的標準下可以正常編譯運行,而且這本書的質量尚可,雖然會在措辭上犯些小錯(比如c++沒有菱形運算符)但不至於花大篇幅去胡說八道。

當然,要想回答這個問題我們得先複習點基礎知識。

<>在c++里的作用

先說結論,在c++里看到<>,絕大多數都是在為模板提供類型參數,當然這種東西我們不討論:(a<1, 2>b),這裡<>是在兩個不同的表達式里。

那既然用來提供類型參數,那為什麼可以啥都不提供呢?答案是有兩類情況確實可以。

第一類是在函數模板上,類型參數可以自動推導時:

template <typename T>
void f(const T&)
{
    std::cout << "f<T>\n";
}
template <>
void f(const int&)
{
    std::cout << "f<int>\n";
}

void f(const int&)
{
    std::cout << "f\n";
}

int main()
{
    f(1);    // f
    f<>(1);  // f<int>
    f(1.2);  // f<T>
}

非模板函數在重載決議中的優先順序總是高於模板的,因此f(1)這樣的表達式總是會用到最下麵定義的那個非模板函數f。這時候我們可以用f<int>(1)來直接調用函數模板f,而函數模板的類型參數如果能從參數推導出來的話,可以不明確給出(也就是後面的f(1.2)那樣的),而在我們現在這句表達式里,我們既要明確使用函數模板,又想讓類型參數被自動推導,就得使用f<>(1)

另一種情況不分類模板還是函數模板,當模板的類型參數有預設值時,可以靠<>來使用這些預設值:

template <typename T = void>
struct Wrapper
{
    using wrappered = T;
};

// Wrapper<> 等於 Wrapper<void>
static_assert(std::is_same_v<Wrapper<>::wrappered, Wrapper<void>::wrappered>);

在第二種情況下,因為沒顯示給出類型參數,且這裡沒法使用類型推導,因此編譯器使用了類型參數的預設值,這裡是void。

觀察比較仔細的話其實會發現上面兩種情況其實是一件事,<>相當於沒有顯示給出任何類型參數,於是對這些沒有顯示指定的類型參數,編譯器會先嘗試類型推導,如果沒法推導則會檢查這些類型參數是否有預設值,有就利用預設值。如果上面這兩步都沒法得到能正常使用的類型參數,模板會被SFINAE淘汰或者報出編譯錯誤。

這並不是什麼新語法,是從有模板開始就一直存在的規則。

現在我們可以看看std::greater<>()是什麼了,首先std::greater是個類模板,然後它接受一個類型參數,這個參數在c++14之後有了預設值void,因此std::greater<>()std::greater<void>()

c++14中究竟添加了什麼

既然c++14並沒有添加“菱形運算符”,那究竟新增了什麼呢?

在已經知道了std::greater<>()的真身後,找起來就很容易了,所以我很快找到了對應的新特性:n3421

這個特性是這樣的:原先我們要用標準庫提供的謂詞模板,需要自己指明參數類型,這樣寫起來很麻煩而且對於那種嵌套的或者元素類型複雜的容器來說寫明參數類型不僅費時而且費力,更要命的是對於map,一不小心是會有性能問題的:

for_each(map.begin(), map.end(), std::pred<std::pair<std::string, int64_t>>());

上述代碼的問題在於正確的參數類型應該是std::pair<const std::string, int64_t>,我們漏掉了const,這會導致pair整個被覆制一遍,性能是無比底下的。要徹底避免這種錯誤,就得利用自動類型推導。

然而前面說了,標準庫提供的謂詞基本全是類模板,類模板的模板參數要麼依賴預設值要麼得顯示指定,怎麼才能依賴自動推導呢。

於是這個新特性最精彩的地方來了:原先的模板的調用運算符不是模板參數也是定死的,但我們可以新加一個預設參數,然後針對這個預設參數的類型進行完全特化,在特化里提供一個泛型的operator(),這樣就能利用函數模板來自動推導參數類型了,而且以前的代碼不受影響。

預設參數的設置也是有講究的,需要用一個謂詞用不到的且不會影響老代碼的類型,運氣不錯,void正好符合條件(void上幾乎沒法做什麼操作,因此也不會被指定給這些謂詞做類型參數),因此現在的greater的代碼是下麵這樣的:

// 註意預設值是void
template <typename T = void> struct greater {
    constexpr bool operator()(const T& lhs, const T& rhs) const 
    {
        return lhs > rhs;
    }
};

// 針對greater<void>的完全特化
template <> struct greater<void> {
    template <class T, class U> auto operator()(T&& t, U&& u) const
    -> decltype(std::forward<T>(t) > std::forward<U>(u))
    { return std::forward<T>(t) > std::forward<U>(u); }
};

當使用std::greater<T>()的時候,代碼的邏輯和原來一樣,當使用std::greater<void>()的時候,返回的Functor的函數調用運算符是個模板,可以自己推導參數類型和返回值類型。至於為啥greater<void>的內部構造可以和其他情況實例化的greater區別這麼大,這個是c++的特性:模板的不同實例之間是可以異構的。

而且因為類型參數的預設值就是void,因此可以簡寫成std::greater<>()

所以c++14只是給標準庫里可以代替運算符的模板們增加了預設類型參數和一個泛型的調用運算符,利用這些可以簡化代碼並確保類型安全。

真相是其實沒啥菱形運算符,只是利用了以前就存在的模板的特性簡化了標準庫的使用,讓人少寫點字。達成的效果倒是和Java的菱形運算符差不多。

總結

顯然書里有誇大成分,老話說盡信書不如無書,還得小心檢驗才是。

順便我們複習了現代c++的重要原則:能依賴自動類型推導的地方,沒必要自己手寫。

因此應該多寫這樣的代碼:std::sort(vec.begin(), vec.end(), std::greater<>());

不過還有最後一個問題,為啥不直接用lambda呢?那是因為能指定類型參數的泛型lambda要在c++20才出現,在這之前想要讓lambda完全做到類型安全得費點功夫,而且lambda整體上也不如直接用標準庫提供的std::greater<>()std::less<>()之類的簡潔易懂。


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

-Advertisement-
Play Games
更多相關文章
  • 大家好,我是 Java陳序員。 今天,給大家介紹一個簡潔、開源的中後臺管理模板項目。 關註微信公眾號:【Java陳序員】,獲取開源項目分享、AI副業分享、超200本經典電腦電子書籍等。 項目介紹 nova-admin —— 一個基於Vue3、Vite5、Typescript、Naive UI, 簡 ...
  • Util UI 已經開發多年, 併在多家公司的項目使用. 不過一直以來, Util UI 存在一些缺陷, 始終未能解決. 最近幾個月, Util 團隊下定決心, 終於徹底解決了所有已知缺陷. Util 應用框架 UI 介紹 Util 應用框架 UI 建立在 Angular , Ng-Zorro, N ...
  • 一、Objects的創建 依據已有的class CPoint ,我們可以產生一個或多個object(對象),或者說是產生一個instance(實體): CPoint aPoint(7.2); // aPoint._x 初始值為 7.2 aPoint.x(5.3); // aPoint._x 現值為 ...
  • 操作系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 Python版本:3.9.12 進行FreeSWITCH會議室相關功能開發過程中,會遇到需要解析會議室列表信息併進行特定操作的情況,比如設置特定通道變數、發送dtmf、錄音等。今天整理下CentOS7環境下,使用Py ...
  • Spirng 當中 Bean的作用域 @目錄Spirng 當中 Bean的作用域每博一文案1. Spring6 當中的 Bean的作用域1.2 singleton 預設1.3 prototype1.4 Spring 中的 bean 標簽當中scope= 屬性其他的值說明1.5 自定義作用域,一個線程 ...
  • title: 深入理解Python多進程:從基礎到實戰 date: 2024/4/29 20:49:41 updated: 2024/4/29 20:49:41 categories: 後端開發 tags: 併發編程 多進程管理 錯誤處理 資源調度 性能優化 非同步編程 Python併發庫 引言 在P ...
  • 本文主要是想給希望開始寫開源項目的同學們一些開源項目維護的實操建議,也算是給自己梳理一下做一個開源項目需要註意的事項。 ...
  • C++ 多態 多態(Polymorphism)是面向對象編程(OOP)的核心概念之一,它允許對象在相同操作下表現出不同的行為。在 C++ 中,多態通常通過繼承和虛函數來實現。 理解多態 想象一個場景,你有一個動物園,裡面有各種動物,如貓、狗、鳥等。每個動物都有自己的叫聲。使用面向對象編程,我們可以創 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...