C++ 的 CTAD 與推斷指示(Deduction Guides)

1 類模板參數推導(CTAD)

1.1 曲線救國

? CTAD 的全稱是類模板參數推導(Class Template Argument Deduction),它允許在實例化類模板時,根據構造函數的參數類型自動推導模板參數,從而避免顯式指定模板參數。CTAD 是在 C++ 17 引入的,在這之前,只有模板函數支持根據函數參數自動推導模板參數,類模板不支持這樣的動作。代碼中實例化類模板必須顯式指定模板參數,十分不便,以致怨聲載道。

? C++ 11 引入了 auto,用作占位符衍生出了一種“工廠函數”慣用法,就是利用函數模板的推導規則,根據函數參數推導出模板參數,然后用推導出的模板參數實例化類對象。比如這個例子:

template<typename T, typename U>
class Foo {
public:Foo(T begin, U end) : m_begin(std::move(begin)), m_end(std::move(end)) {}
private:T m_begin;U m_end;
};template<typename T, typename U>
auto MakeFoo(T begin, U end) {return Foo<T, U>{begin, end};
}auto f2 = MakeFoo(42, 5.24);

1.2 隱式規則

? C++ 17 的 CTAD 默認通過類模板的構造函數定義模板參數的推導規則,和函數模板一樣,由構造函數的實參類型決定模板的參數類型。比如上一節的 Foo 類,不需要工廠函數,可以直接這樣用:

Foo f1{42, 5.24};

但是編譯器對類模板參數的推導是有條件的,那就是構造函數的形式參數列表必須能覆蓋全部模板參數,并且這些形參都必須參與推導,不能有在非推導語境中的模板參數。簡單來說,以下兩個類模板就不支持 CTAD 隱式推導:

template<typename T, typename U>
struct Bar {Bar(const T& t) {}
};template<typename T, typename U>
struct Widget {Widget(const T& t, typename std::type_identity_t<U> u) {}
};Bar b1(42); //錯誤
Widget w(1, 2.3); //錯誤,不能實例化 Widget<int, double> 類型

Bar 的構造函數形參列表只覆蓋了一個模板參數,另一個未知,不能通過構造函數同時推導出T 和 U 的類型。Widget 同樣不支持 CTAD,它的構造函數形參覆蓋了兩個模板參數,但是 U 出現在典型的非推導語境中,它不參與推導,編譯器不會根據實參 2.3 去推導 U 為 double,所以不能同時確定 T 和 U 的類型,也就無法實例化 Widget<int, double> 類型。

1.3 演化

? CTAD 在 C++ 20 改善了一下對聚合類型的支持。對于聚合類型,可以在不提供顯式構造函數的情況下,按照聚合類型的初始化順序實現類型推導。我們假設下面例子中的 Foo 是個聚合類型,為什么假設呢?因為是不是聚合類型還要看它那三個成員的類型,我們這里給出的例子能確保 Foo 實例化后是個聚合類型。

template<typename T, typename U, typename V>
struct Foo {T t;U u;V v;
};Foo f{ 1, 2.3, "Hello" };

大括號中的參數,按照按照聚合類型的初始化順序,以及傳值類型模板形參的推導規則,依次與 t、u 和 v 匹配,推導出 T、U 和 V 的類型為 int、double 和 const char* ,并用 Foo<int, double, const char*> 類型初始化 f。

2 推斷指示(Deduction Guides)

2.1 什么是推斷指示

? 盡管 CTAD 可以根據構造函數參數自動推導模板參數,但有些復雜情況下,隱式的規則可能無法滿足需求。此時我們可以利用 C++ 17 的顯示推斷指示(推斷指引),通過提供自定義的模板參數推導規則,讓編譯器知道如何確定類模板的模板參數,從而實現復雜類模板參數的自動推導。

? 推斷指示的語法大概是這個樣子的:

//deduction-guide:
explicit(opt) template-name (parameter-declaration-clause) -> simple-template-id ;

explicit 關鍵字是可選的,用于說明是否是顯式推斷指示。這個語法的重點是 減號和大于號組成的箭頭符號(->),箭頭符號左邊的 template-name 必須與箭頭符號右邊的 simple-template-id 具有相同的標識符。此外,如果一個 template-name 有多個推斷指引,那么它們的 parameter-declaration-clause 不能相同。以 std::tuple 為例,看看它的推斷指引的語法:

template<class... UTypes>
tuple(UTypes...) -> tuple<UTypes...>;

箭頭符號的左邊是 std::tuple 的構造函數(之一),其中 UTypes… 就是傳遞給構造函數的參數包(就是 parameter-declaration-clause)。箭頭符號的右邊是 std::tuple 的模板參數(simple-template-id),這個語法告訴編譯器,可以根據構造函數的參數推斷對應的類模板實例化使用的模板參數。

2.2 推斷指示的典型用法

? ContainerT 類有一個符合 CTAD 的構造函數,c1 就是通過這個構造函數提供的隱式規則,推導出 c1 的類型是 ContainerT。但是當我們希望傳遞一個大括號列表的時候,我們希望 T 是一個 vector 容器類型,此時構造函數提供的默認規則就無能為力了。c2 的定義會導致編譯錯誤,因為模板形參推導不支持大括號列表(auto 的推導支持將大括號列表推導為具體的 std::initializer_list 類型,但這是個寫死的規則,算不上推導)。

template <typename T>
class ContainerT {
public:ContainerT(T value) : val(value) {}T val;
};ContainerT c1(5); //ContainerT<int>
ContainerT c2({ 1, 5, 8 }); //錯誤

? 為了達成目標,我們需要為 ContainerT 類模板提供一個顯式推斷指示,通過顯式推斷指示明確模板參數 T 是 vector 類型。這是我們提供的推斷指示:

template <typename U>
ContainerT(std::initializer_list<U>) -> ContainerT<std::vector<U>>;

函數形參不支持自動推導成 std::initializer_list,我們干脆寫死就是 std::initializer_list,它與大括號列表是可以匹配的,相當于只需推導 std::initializer_list 的模板參數 U。當確定了 U 之后,我們希望 ContainerT 的模板參數是 std::vector,這就是這條顯式推斷指示的語法解釋。有了這條推斷指示,c2 的定義就合法了,并且也得到了我們希望的 ContainerT<std::vector> 類型。

? 再來看一個稍微復雜一點的例子:

template<typename T>
class Foo {
public:Foo(T value) {m_values.push_back(std::move(value));}template<class Iter>Foo(Iter begin, Iter end) {std::copy(begin, end, std::back_inserter(m_values));}
private:std::vector<T> m_values;
};Foo f1{ 5 }; // Foo<int> std::vector<int> vi{ 1, 3, 5, 7 };
Foo f2{ vi.begin(), vi.end() }; //錯誤

Foo 有兩個構造函數,第一個構造函數配合 CTAD,使得 f1 的定義沒有問題,但是 f2 的定義不被編譯器支持,因為通過構造函數傳遞的兩個迭代器,編譯器無法推斷出模板參數 T 的類型。當我們拿到一對迭代器的時候,我們可以通過類型萃取獲得迭代器的值類型,可以將這個值類型指代類模板參數 T。

? 按照這個思路得到推斷指示:

template<class Iter>
Foo(Iter begin, Iter end)->Foo<typename std::iterator_traits<Iter>::value_type>;

有了這個顯式推斷指示,上面例子代碼中 f2 的定義就合法了,并且得到的 f2 的類型也是我們希望的 Foo 類型。

2.3推斷指示的非典型用法

? 顯示推斷指引可以用在一些需要提供類模板特化版本的場合,比如下面這個例子中的 Foo 類模板,當面對指針類型的時候,比如字符串字面量,如果按照默認的構造函數提供的 CTAD,T 被推導為指針,成員 m_t 只保存了字符串的指針,在很多情況下,這都是比較危險的,一不小心就出現野指針訪問。傳統方法是針對指針類型提供特化版本,就如同這個例子一樣。

template<class T>
struct Foo {Foo(T t) { m_t = t; }T m_t;
};//特化版本
template<>
struct Foo<const char*> {Foo(const char* t) { m_t = t; }std::string m_t;
};

? 提供特化版本也沒什么不妥,就是要敲很多鍵盤。但是如果用顯式推斷指示,只需一行代碼就可以了:

//推斷指引
Foo(const char*)->Foo<std::string>;

少敲幾次鍵盤,還不需要提供函數體的代碼,通過類型的指示,復用原來的構造函數,有什么利用不用推斷指示?

3 總結

? CTAD 拖了這么長時間實在氣憤,好在顯式推斷指示讓類模板參數的自動推導比函數模板的模式匹配強大 N 倍,也就沒那么大的氣了。顯式推斷指示在標準庫中也是大量引用,比如你可以這樣定義一個 array:

std::array arr{1, 2, 3, 4, 5};

因為它有一條這樣的推斷指示:

template <class... T>
array(T&&... t) -> array<std::common_type_t<T...>, sizeof...(T)>;

參考資料

[1] Marc Gregoire, Professional C++ (Fifth Edition), John Wiley & Sons, Inc., 2021

[2] https://en.cppreference.com/w/cpp/language/class_template_argument_deduction

[3] Nicolai M. Josuttis, C++20 - The Complete Guide, http://leanpub.com/cpp20’

[4] Jacek Galowicz. C++17 STL Cookbook. Packtpub. 2017

[5] P0702:Language support for Constructor Template Argument Deduction

[6] CWG 2628:Implicit deduction guides should propagate constraints

關注作者的算法專欄
https://blog.csdn.net/orbit/category_10400723.html

關注作者的出版物《算法的樂趣(第二版)》
https://www.ituring.com.cn/book/3180

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/66344.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66344.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66344.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Shell正則表達式與文本處理三劍客(grep、sed、awk)

一、正則表達式 Shell正則表達式分為兩種&#xff1a; 基礎正則表達式&#xff1a;BRE&#xff08;basic regular express&#xff09; 擴展正則表達式&#xff1a;ERE&#xff08;extend regular express&#xff09;&#xff0c;擴展的表達式有、?、|和() 1.1 基本正則表…

掌握 React 高階組件與高階函數:構建可復用組件的新境界

一、引言 在 React 開發中&#xff0c;代碼復用性和邏輯分離是提高開發效率和維護性的重要手段。高階組件&#xff08;Higher-Order Component, HOC&#xff09;和高階函數&#xff08;Higher-Order Function, HOF&#xff09;是實現這一目標的兩種強大工具。本文將詳細介紹這…

arcgis提取不規則柵格數據的矢量邊界

效果 1、準備數據 柵格數據:dem或者dsm 2、柵格重分類 分成兩類即可 3、新建線面圖層 在目錄下選擇預先準備好的文件夾,點擊右鍵,選擇“新建”→“Shapefile”,新建一個Shapefile文件。 在彈出的“新建Shapefile”對話框內“名稱”命名為“折線”,“要素類型”選…

阿里云通義實驗室自然語言處理方向負責人黃非:通義靈碼2.0,邁入 Agentic AI

通義靈碼是基于阿里巴巴通義大模型研發的AI 智能編碼助手&#xff0c;在通義靈碼 1.0 時代&#xff0c;我們針對代碼的生成、補全和問答&#xff0c;通過高效果、低時延&#xff0c;研發出了國內最受歡迎的編碼助手。 在通義靈碼 2.0 發布會上&#xff0c;阿里云通義實驗室自然…

Open3D 最小二乘擬合平面(直接求解法)【2025最新版】

目錄 一、算法原理二、代碼實現三、結果展示本文由CSDN點云俠原創,原文鏈接。如果你不是在點云俠的博客中看到該文章,那么此處便是不要臉的爬蟲與GPT。 博客長期更新,本文最近更新時間為:2025年1月18日。 一、算法原理 平面方程的一般表達式為:

超標量處理器設計2-cache

1. cache 介紹 影響Cache缺失的情況有3種&#xff1a; Compulsory: 第一次被訪問的指令或者數據肯定不會在cache中&#xff0c;需要通過預取來減少這種缺失Capcity: Cache容量越大&#xff0c;缺失就可以更少, 程序頻繁使用的三個數據來源于3個set&#xff0c; 但是&#xff…

linux 安裝PrometheusAlert配置釘釘告警

在 Linux 上安裝 PrometheusAlert 并配置釘釘告警的步驟如下: 1. 準備工作 釘釘機器人: 在釘釘群中創建一個機器人,獲取 Webhook URL。示例 Webhook URL:https://oapi.dingtalk.com/robot/send?access_token=your_dingtalk_token。PrometheusAlert 安裝包: 從 Prometheus…

當PHP遇上區塊鏈:一場奇妙的技術之旅

PHP 與區塊鏈的邂逅 在技術的廣袤宇宙中&#xff0c;區塊鏈技術如同一顆耀眼的新星&#xff0c;以其去中心化、不可篡改、透明等特性&#xff0c;掀起了一場席卷全球的變革浪潮。眾多開發者懷揣著對新技術的熱忱與探索精神&#xff0c;紛紛投身于區塊鏈開發的領域&#xff0c;試…

vscode的安裝與使用

下載 地址&#xff1a;https://code.visualstudio.com/ 安裝 修改安裝路徑&#xff08;不要有中文&#xff09; 點擊下一步&#xff0c;創建桌面快捷方式&#xff0c;等待安裝 安裝中文插件 可以根據自己的需要安裝python和Jupyter插件

32單片機綜合應用案例——物聯網(IoT)環境監測站(四)(內附詳細代碼講解!!!)

無論你身處何種困境&#xff0c;都要堅持下去&#xff0c;因為勇氣和毅力是成功的基石。不要害怕失敗&#xff0c;因為失敗并不代表終結&#xff0c;而是為了成長和進步。相信自己的能力&#xff0c;相信自己的潛力&#xff0c;相信自己可以克服一切困難。成功需要付出努力和堅…

淺談云計算19 | OpenStack管理模塊 (上)

OpenStack管理模塊&#xff08;上&#xff09; 一、操作界面管理架構二、認證管理2.1 定義與作用2.2 認證原理與流程2.2.1 認證機制原理2.2.2 用戶認證流程 三、鏡像管理3.1 定義與功能3.2 鏡像服務架構3.3 工作原理與流程3.3.1 鏡像存儲原理3.3.2 鏡像檢索流程 四、計算管理4.…

RK3568 Android11 鎖屏界面屏蔽下拉狀態欄

參考文章&#xff1a; Android R鎖屏界面屏蔽下拉狀態欄_pulseexpansionhandler-CSDN博客 前提增加狀態欄控制顯隱屬性&#xff0c;以下面文章為前提補充功能 RK3568 Android11 狀態欄和導航欄增加顯示控制功能-CSDN博客 修改文件位置&#xff1a; frameworks/base/package…

彩色圖像面積計算一般方法及MATLAB實現

一、引言 在數字圖像處理中&#xff0c;經常需要獲取感興趣區域的面積屬性&#xff0c;下面給出圖像處理的一般步驟。 1.讀入的彩色圖像 2.將彩色圖像轉化為灰度圖像 3.灰度圖像轉化為二值圖像 4.區域標記 5.對每個區域的面積進行計算和顯示 二、程序代碼 %面積計算 cle…

分布式理解

分布式 如何理解分布式 狹義的分布是指&#xff0c;指多臺PC在地理位置上分布在不同的地方。 分布式系統 分布式系**統&#xff1a;**多個能獨立運行的計算機&#xff08;稱為結點&#xff09;組成。各個結點利用計算機網絡進行信息傳遞&#xff0c;從而實現共同的“目標或者任…

深入了解卷積神經網絡(CNN):圖像處理與深度學習的革命性技術

深入了解卷積神經網絡&#xff08;CNN&#xff09;&#xff1a;圖像處理與深度學習的革命性技術 導語 卷積神經網絡&#xff08;CNN&#xff09;是現代深度學習領域中最重要的模型之一&#xff0c;特別在計算機視覺&#xff08;CV&#xff09;領域具有革命性的影響。無論是圖…

QT:IconButton的動畫效果

要實現IconButton&#xff0c;需要處理背景。參考&#xff1a; QT之IconWidget-CSDN博客 隨后就是Button的按下動畫效果。實現也簡單。思路就是記錄按下狀態&#xff0c;然后在繪制時偏移一個像素&#xff08;也可以繪制另外一個圖&#xff09;。 增加一個字段&#xff0c;記…

Android渲染Latex公式的開源框架比較

對比主流框架&#xff0c;介紹如下幾款 1、AndroidMath 官網&#xff1a;https://github.com/gregcockroft/AndroidMath/tree/master 基于android原生view方式渲染 優點&#xff1a;速度快&#xff0c;開源協議 MIT license 缺點&#xff1a;不支持文字公式混合渲染 2、Ma…

Red Hat8:搭建FTP服務器

目錄 一、匿名FTP訪問 1、新建掛載文件 2、掛載 3、關閉防火墻 4、搭建yum源 5、安裝VSFTPD 6、 打開配置文件 7、設置配置文件如下幾個參數 8、重啟vsftpd服務 9、進入圖形化界面配置網絡 10、查看IP地址 11、安裝ftp服務 12、遇到拒絕連接 13、測試 二、本地…

VS Code--常用的插件

原文網址&#xff1a;VS Code--常用的插件_IT利刃出鞘的博客-CSDN博客 簡介 本文介紹VS Code&#xff08;Visual Studio Code&#xff09;常用的插件。 插件的配置 默認情況下&#xff0c;插件會放到這里&#xff1a;C:\Users\xxx\.vscode\extensions 修改插件位置的方法 …

Re78 讀論文:GPT-4 Technical Report

諸神緘默不語-個人CSDN博文目錄 諸神緘默不語的論文閱讀筆記和分類 論文全名&#xff1a;GPT-4 Technical Report 官方博客&#xff1a;GPT-4 | OpenAI appendix懶得看了。 文章目錄 1. 模型訓練過程心得2. scaling law3. 實驗結果減少風險 1. 模型訓練過程心得 模型結構還…