C++慣用法: 通過std::decltype來SFINAE掉表達式

目錄

1.什么是SFINAE

2.SFINAE(替換失敗不是錯誤)

3.通過std::decltype來SFINAE掉表達式


1.什么是SFINAE

????????SFINAE 技術,即匹配失敗不是錯誤,英文Substitution Failure Is Not An Error,其作用是當我們在進行模板特化的時候,會去選擇那個正確的模板,避免失敗。

????????SFINAE一般用于函數重載和編譯期間類型檢查,標準庫中很多type traits模板就是通SFINAE來實現的。

????????看個具體的例子:

#include <iostream>
#include <type_traits>
using namespace std;template<typename T>
struct check_has_member_id
{// 僅當T是一個類類型時,“U::*”才是存在的,從而這個泛型函數的實例化才是可行的// 否則,就將觸發SFINAEtemplate<typename U>static void check(decltype(&U::id)){}// // 僅當觸發SFINAE時,編譯器才會“被迫”選擇這個版本template<typename U>static int check(...){}enum {value = std::is_void<decltype(check<T>(NULL))>::value};
};struct TEST_STRUCT
{int rid;
};struct TEST_STRUCT2
{int id;
};int main()
{check_has_member_id<TEST_STRUCT> t1;cout << t1.value << endl;check_has_member_id<TEST_STRUCT2> t2;cout << t2.value << endl;check_has_member_id<int> t3;cout << t3.value << endl;return 0;
}
// g++ --std=c++11  xxx.c

????????核心的代碼是在實例化check_has_member_id對象的時候,通過模板參數T的類型,決定了結構體中對象value的值。而value的值是通過check<T>函數的返回值是否是void決定的。如果T中含有id成員的話,那么就會匹配第一個實例,返回void;如果不包含id的話,會匹配默認的實例,返回int。

????????利用這個機制還可以做很多類似的判斷,比如判斷一個類是否是結構體。

#include <iostream>
#include <type_traits>// 2. 判斷變量是否是一個struct 或者 類
// https://www.jianshu.com/p/d09373b83f86
template <typename T>
struct check
{template <typename U>static void check_class(int U::*) {}template <typename U>static int check_class(...) {}enum { value = std::is_void<decltype(check_class<T>(0))>::value };
};class myclass {};int main()
{check<myclass> t;std::cout << t.value << std::endl;check<int> t2;std::cout << t2.value << std::endl;return 0;
}

std::is_void的用法可參考:

C++17之std::void_t-CSDN博客

2.SFINAE(替換失敗不是錯誤)

????????在一個函數調用的備選方案中包含函數模板時,編譯器首先要決定應該將什么樣的模板參數 用于各種模板方案,然后用這些參數替換函數模板的參數列表以及返回類型,最后評估替換 后的函數模板和這個調用的匹配情況(就像常規函數一樣)。

????????但是這一替換過程可能會遇到問題:替換產生的結果可能沒有意義。不過這一類型的替換不會導致錯誤,C++語言規則要 求忽略掉這一類型的替換結果。

????????考慮如下的例子:

// number of elements in a raw array:
template<typename T, unsigned N>
std::size_t len (T(&)[N])
{return N;
}
// number of elements for a type having size_type:
template<typename T>
typename T::size_type len (T const& t)
{return t.size();
}

當傳遞的參數是裸數組或者字符串常量時,只有那個為裸數組定義的函數模板能夠匹配:

int a[10];
std::cout << len(a); // OK: only len() for array matches
std::cout << len("tmp"); //OK: only len() 

????????如果只是從函數簽名來看的話,對第二個函數模板也可以分別用 int[10]和 char const [4]替換 類型參數 T,但是這種替換在處理返回類型 T::size_type 時會導致錯誤。因此對于這兩個調用, 第二個函數模板會被忽略掉。

????????如果傳遞的是裸指針,以上兩個模板都不會被匹配上(但是不會因此而報錯)。此時編譯 期會抱怨說沒有發現合適的 len()函數:

int* p;
std::cout << len(p); // ERROR: no matching len() function found

但是這和傳遞一個有 size_type 成員但是沒有 size()成員函數的情況不一樣。比如如果傳遞的參數是 std::allocator<>:

std::allocator<int> x;
std::cout << len(x); // ERROR: len() function found, but can’t size()

此時編譯器會匹配到第二個函數模板。因此不會報錯說沒有發現合適的 len()函數,而是會 報一個編譯期錯誤說對 std::allocator而言 size()是一個無效調用。此時第二個模板函數不 會被忽略掉。

如果忽略掉那些在替換之后返回值類型為無效的備選項,那么編譯器會選擇另外一個參數類 型匹配相差的備選項。比如:?

// number of elements in a raw array:
template<typename T, unsigned N>
std::size_t len (T(&)[N])
{return N;
}
// number of elements for a type having size_type:
template<typename T>
typename T::size_type len (T const& t)
{return t.size();}
// 對所有類型的應急選項:
std::size_t len (…)
{return 0;
}

????????此處額外提供了一個通用函數 len(),它總會匹配所有的調用,但是其匹配情況也總是所有 重載選項中最差的(通過省略號...匹配)。

????????對于指針,只有應急選項能夠匹配上,此時編譯器不會再報缺少適用 于本次調用的 len()。不過對于 std::allocator的調用,雖然第二個和第三個函數都能匹配 上,但是第二個函數依然是最佳匹配項。因此編譯器依然會報錯說缺少 size()成員函數。

3.通過std::decltype來SFINAE掉表達式

????????對于有些限制條件,并不總是很容易地就能找到并設計出合適的表達式來 SFINAE 掉函數模 板。

????????比如,對于有 size_type 成員但是沒有 size()成員函數的參數類型,我們想要保證會忽略掉函 數模板 len()。如果沒有在函數聲明中以某種方式要求 size()成員函數必須存在,這個函數模 板就會被選擇并在實例化過程中導致錯誤:

template<typename T>
typename T::size_type len (T const& t)
{return t.size();
}
std::allocator<int> x;
std::cout << len(x) << ’\n’; //ERROR: len() selected, 

處理這種情況有一個常見的模式或者習慣用法:

1)通過尾置返回類型語法(函數名前用auto修飾,并在函數名后跟->,再加末尾的返回類型) 來制定返回類型。

2)使用std::decltype和逗號運算符來定義返回類型。

3)將所有必須成立的表達式放置于逗號運算符開頭(表達式轉換為void類型,以防逗號運算符重載)。

4)在逗號運算符末尾定義一個實際返回類型(類型為返回類型)的對象。

例如:

template<typename T>
auto len (T const& t) -> decltype( (void)(t.size()), T::size_type() )
{return t.size();
}

這里返回類型定義為:

decltype( (void)(t.size()), T::size_type() )

? ? ? ? 由于decltype構造的操作數是以逗號分隔的表達式列表,因此,最后一個表達式T::size_type()生成所需返回類型的值(decltype將其轉換為返回類型)。(最后一個)逗號之前的表達式是必須成立的。在本例中就是t.size()。將表達式強制轉換為void,是為了避免由于用戶自定義重載表達式對于類型的逗號運算符而帶來的問題。

? ? ? ? 請注意,decltype的實參是一個未求值的操作數。這意味著可以在不調用構造函數的情況下創建"虛對象",請參考:

C/C++中decltype關鍵字用法總結_c++ decltype用法-CSDN博客

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

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

相關文章

嵌入式c語言——指針加修飾符

指針變量可以用修飾符來修飾

量化交易常用名詞介紹(七)——模塊篇

目錄 七、模塊篇 1. NumPy 2. pandas 3. matplotlib 4. scikit-learn 5. TensorFlow 6. TA-Lib 7. statsmodels 8. Backtrader 9. PyPortfolioOpt 10. Zipline 七、模塊篇 在量化交易中&#xff0c;Python 及其豐富的庫生態系統提供了強大的支持。以…

Redis部署和基礎命令

一、Redis基本概念 1.1 Redis簡介 Redis&#xff08;遠程字典服務器&#xff09; 是一個開源的、使用 C 語言編寫的 NoSQL 數據庫。 Redis 基于內存運行并支持持久化&#xff0c;采用key-value&#xff08;鍵值對&#xff09;的存儲形式&#xff0c;是目前分布式架構中不可或…

python爬蟲之scrapy基于管道持久化存儲操作

python爬蟲之scrapy基于管道持久化存儲操作 本文基于python爬蟲之基于終端指令的持久化存儲和python爬蟲之數據解析操作而寫 scrapy持久化存儲 基于管道&#xff1a; 編碼流程&#xff1a; 1、數據解析 2、在item類中定義相關屬性 3、將解析的數據封裝存儲到item類型的對象 4、…

Linux內核 -- 內存管理之scatterlist結構使用

Linux Kernel Scatterlist 使用指南 1. 簡介 scatterlist 結構在 Linux 內核中主要用于 DMA&#xff08;直接內存訪問&#xff09;操作中的內存管理。它允許將不連續的物理內存片段表示為一個邏輯上的連續塊&#xff0c;從而使 DMA 操作可以高效地處理這些不連續的內存片段。…

【問題記錄】VsCode中以管理員權限運行Powershell

問題展示 今天在嘗試運行nodemon命令的時候出問題&#xff0c;顯示沒法識別&#xff0c;經過分析發現是管理員權限的問題&#xff0c;由于是在vscode里面進行開發&#xff0c;因此特此進行配置。 方法一 直接在vscode命令行中輸入如下命令&#xff1a; Start-Process powers…

IDEA如何創建原生maven子模塊

文件 -> 新建 -> 新模塊 -> Maven ArcheTypeMaven ArcheType界面中的輸入框介紹 名稱&#xff1a;子模塊的名稱位置&#xff1a;子模塊存放的路徑名創建Git倉庫&#xff1a;子模塊不單獨作為一個git倉庫&#xff0c;無需勾選JDK&#xff1a;JDK版本號父項&#xff1a;…

Linux網絡命令:網絡工具socat詳解

目錄 一、概述 二、基本用法 1、基本語法 2、常用選項 3、獲取幫助 三、用法示例 1. 監聽 TCP 端口并回顯接收到的數據 2. 通過 TCP 端口轉發數據到 UNIX 套接字 3. 將文件內容發送到 TCP 端口&#xff1a; 4. 使用偽終端進行串行通信 5、啟動一個TCP服務器 6、建…

Hi3861鴻蒙開發環境搭建

1.1 安裝配置Visual Studio Code 打開Download Visual Studio Code - Mac, Linux, Windows選擇下載安裝Windows系統的Visual Studio Code。 下載后進行安裝。Visual Studio Code安裝完成后&#xff0c;通過內置的插件市場搜索并安裝開發所需的插件如圖所示&#xff1a; 1.2 安…

實時消息推送系統,寫得太好了!

websocket 協議是在 http 協議上的一種補充協議&#xff0c;是 html5 的新特性&#xff0c;是一種持久化的協議。其實 websocket 和 http 關系并不是很大&#xff0c;不過都是屬于應用層的協議&#xff0c;接下來我們就開始實戰。 websocket 定時推送 本教程基于 springboot …

symbol數據類型以及應用場景

在js中,Symbol是一種基本數據類型,是在ECMAScript 6 (ES6) 中引入的新特性。表示獨一無二 Symbol的定義 Symbol是不完整的構造函數&#xff0c;創建symbol對象時不需要new操作符,原因是通過 new 實例化的結果是一個 object 對象&#xff0c;而不是原始類型的 symbol。 var s…

STL--棧(stack)

stack 棧是一種只在一端(棧頂)進行數據插入(入棧)和刪除(出棧)的數據結構,它滿足后進先出(LIFO)的特性。 使用push(入棧)將數據放入stack,使用pop(出棧)將元素從容器中移除。 使用stack,必須包含頭文件: #include<stack>在頭文件中,class stack定義如下: namespace std…

Druid 連接池在很多方面表現出色,但在實際應用中也可能會遇到一些缺陷或問題。

Druid 連接池是阿里巴巴開源的一個功能強大的數據庫連接池&#xff0c;它具有高性能、可靠性、可管理性、安全性和擴展性等特點。然而&#xff0c;盡管 Druid 連接池在很多方面表現出色&#xff0c;但在實際應用中也可能會遇到一些缺陷或問題。 1. **連接耗盡問題**&#xff1…

13 - matlab m_map地學繪圖工具基礎函數 - 介紹創建管理顏色映射的函數m_colmap和輪廓圖繪制顏色條的函數m_contfbar

13 - matlab m_map地學繪圖工具基礎函數 - 介紹創建管理顏色映射的函數m_colmap和輪廓圖繪制顏色條的函數m_contfbar 0. 引言1. 關于m_colmap2. 關于m_contfbar3. 結語 0. 引言 本篇介紹下m_map中用于創建和管理顏色映射函數&#xff08;m_colmap&#xff09;和 為輪廓圖繪制顏…

基于深度學習的電影推薦系統

1 項目介紹 1.1 研究目的和意義 在電子商務日益繁榮的今天&#xff0c;精準預測商品銷售數據成為商家提升運營效率、優化庫存管理以及制定營銷策略的關鍵。為此&#xff0c;開發了一個基于深度學習的商品銷售數據預測系統&#xff0c;該系統利用Python編程語言與Django框架&a…

SQLite 命令行客戶端 + Windows 批處理應用

SQLite 命令行客戶端 Windows 批處理應用 下載 SQLite 客戶端1. Bat 輔助腳本1. 執行SQL.bat執行 2. 導出Excel.bat執行效果 3. 導出HTML.bat執行效果 4. 清空-訂單表.bat5. 訂單表.bat 2. 測試 SQL1. 創建訂單表.sql2. 插入訂單表.sql3. 查詢訂單表.sql4. 清空訂單表.sql5. 刪…

Qt Qwt 圖表庫詳解及使用

文章目錄 Qt Qwt 圖表庫詳解及使用一、Qwt 概述二、安裝 Qwt1. 下載和編譯 Qwt2. 在項目中使用 Qwt三、Qwt 的基本使用1. 創建一個簡單的折線圖2. 添加圖例和自定義樣式四、Qwt 的交互功能1. 啟用縮放和平移2. 啟用數據點選擇五、Qwt 的高級特性1. 實時數據更新2. 多軸繪圖六、…

nvm 管理多版本 node

1、下載 先不安裝node 下載 nvm 1.1.10-setup.zip 解壓&#xff1a;nvm&#xff1a;https://nvm.uihtm.com/ 新建nodejs/node、nodejs/nvm文件夾用于存放node版本和nvm安裝路徑 安裝nvm&#xff1a;上述鏈接有安裝教程 查看是否安裝成功&#xff1a;重新打開cmd 輸入 nvm nv…

Hyper-V克隆虛擬機教程分享!

方法1. 使用導出導入功能克隆Hyper-V虛擬機 導出和導入是Hyper-V服務器備份和克隆的一種比較有效的方法。使用此功能&#xff0c;您可以創建Hyper-V虛擬機模板&#xff0c;其中包括軟件、VM CPU、RAM和其他設備的配置&#xff0c;這有助于在Hyper-V中快速部署多個虛擬機。 在…

深入理解基本數據結構:數組詳解

引言 在計算機科學中&#xff0c;數據結構是存儲、組織和管理數據的方式。數組作為最基礎的數據結構之一&#xff0c;廣泛應用于各種編程場景。在這篇博客中&#xff0c;我們將詳細探討數組的定義、特點、操作及其在不同編程語言中的實現。 什么是數組&#xff1f; 數組是一種…