日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

C++ SFINAE簡介和std::enable_if_t的簡單使用

作者:wangx_x 更新時(shí)間: 2022-02-13 編程語言

最近整理代碼時(shí)發(fā)現(xiàn)了有人常會使用std::enable_if_t,據(jù)說這個(gè)是C++14才支持的寫法,因此再次勾起了我的整理欲。但要是熟悉std::enable_if的話其實(shí)也沒啥太大難度,自認(rèn)為這種使用方式主要提供了一種通過模板偏特化來實(shí)現(xiàn)的類型篩選機(jī)制,某些情況下在設(shè)計(jì)復(fù)雜工程的泛化處理時(shí)能提供一些方便。但能力有限,目前我還沒有發(fā)現(xiàn)哪些非常典型的使用場景能大幅提升性能。

不過整理之前感覺有必要先引入一個(gè)很重要的概念:SFINAE,這是英文Substitution failure is not an error的縮寫,意思是匹配失敗不是錯(cuò)誤。這句話的意思是:我們使用模板函數(shù)時(shí)編譯器會根據(jù)傳入的參數(shù)來推導(dǎo)適配最合適的模板函數(shù),在某些情況下,推導(dǎo)過程會發(fā)現(xiàn)某一個(gè)或者某幾個(gè)模板函數(shù)推導(dǎo)起來其實(shí)是無法編譯通過的,但只要有一個(gè)可以正確推導(dǎo)并編譯出來,則那些推導(dǎo)得到的可能產(chǎn)生編譯錯(cuò)誤的模板函數(shù)就并不會引發(fā)編譯錯(cuò)誤,即匹配失敗不是錯(cuò)誤。下面舉個(gè)栗子:

struct testA 
{
	int data;
	testA(int val) :data(val){};
};
struct testB :testA
{
	typedef double value;
	testB(int val) :testA(val){};
};
template<typename T>
typename T::value add(T t1, T t2)	// Definition #1
{
	return t1.data + t2.data;
}
int add(testA t1, testA t2)	// Definition #2
{
	return t1.data + t2.data;
}

從代碼編寫角度來說,乍一看總感覺模板函數(shù)存在有問題:在未明確輸入類型是testA還是testB時(shí)就貿(mào)然使用了其中的value類型,如果是其他場景一般會引起編譯器的報(bào)錯(cuò),理論上著實(shí)也不甚嚴(yán)謹(jǐn)。 但依托于模板特化的運(yùn)作機(jī)制,編譯器進(jìn)行類型推導(dǎo)時(shí)會嘗試適配該模板,SFINAE特性會引導(dǎo)編譯器在遭遇特化失敗后放棄該模板并轉(zhuǎn)向其他函數(shù),且不會報(bào)錯(cuò)。
下面時(shí)執(zhí)行結(jié)果:

int _tmain(int argc, _TCHAR* argv[]){
	testA a(1), b(2);
	testB c(3), d(4);
	add(a, b);	// Call #2. Without error (even though there is no testA::value) thanks to SFINAE.
	add(c, d);	// Call #1
	return 0;
}

當(dāng)然,我這個(gè)小例子中使用了一些對于結(jié)構(gòu)體struct的派生繼承,其實(shí)對于C++來說,對于struct的使用也已經(jīng)提升到了新的層面,其中的使用場景我一會再總結(jié)個(gè)小文章吧,點(diǎn)擊前往

現(xiàn)在開始描述下std::enable_if的使用方式吧,std::enable_if顧名思義,滿足條件時(shí)類型有效。作為選擇類型的小工具,其廣泛的應(yīng)用在 C++ 的模板元編程(meta programming)中。基本實(shí)現(xiàn)方式大約為:

template<bool B, class T = void>
struct enable_if {};
 
template<class T>
struct enable_if<true, T> { typedef T type; };

一個(gè)是普通版本的模板類定義,一個(gè)偏特化版本的模板類定義。
主要在于第一個(gè)參數(shù)是否為true,當(dāng)?shù)谝粋€(gè)模板參數(shù)為false的時(shí)候并不會定義type,只有在第一模板參數(shù)為true的時(shí)候才會定義type

typename std::enable_if<true, int>::type t; //正確,type等同于int
typename std::enable_if<true>::type; //可以通過編譯,沒有實(shí)際用處,推導(dǎo)的模板是偏特化版本,第一模板參數(shù)是true,第二模板參數(shù)是通常版本中定義的默認(rèn)類型即void,但是一般也用不上它。
typename std::enable_if<false>::type; //無法通過編譯,type類型沒有定義
typename std::enable_if<false, int>::type t2; //同上

網(wǎng)上扒過來了一個(gè)用于偏特化的小例子:

template <typename T>
typename std::enable_if<std::is_trivial<T>::value>::type SFINAE_test(T value)
{
    std::cout<<"T is trival"<<std::endl;
}

template <typename T>
typename std::enable_if<!std::is_trivial<T>::value>::type SFINAE_test(T value)
{
    std::cout<<"T is none trival"<<std::endl;
}

下面是一個(gè)用于校驗(yàn)函數(shù)模板參數(shù)類型的小例子:

template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
is_odd(T t) {
  return bool(t%2);
}
 
template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type>
bool is_even(T t) {
  return !is_odd(t); 
}

到這里對于enable_if_t就更通俗易懂了:

template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;

可以直接拿來作為類型來使用,以下是我從網(wǎng)上找的一些簡單事例。
1、作為函數(shù)參數(shù)或返回值:

template<typename T>
struct Check1
{
    //如果T的類型是int,則定義函數(shù) int read(void* = nullptr)
	template<typename U = T>
	U read(typename std::enable_if_t<std::is_same_v<U, int> >* = nullptr) {
		return 42;
	}
    
    //如果T的類型是double,則定義函數(shù) double read()
	template<typename U = T>
	typename std::enable_if_t<std::is_same_v<U, double>, U> read() {
		return 3.14;
	}
}

作為模板參數(shù):

template<typename T>
struct Check2
{
    //如果T的類型是int,則定義函數(shù) int read()
	template<typename U = T, typename std::enable_if_t<std::is_same_v<U, int>, int> = 0>
	U read() {
		return 42;
	}
    
    //如果T的類型是double,則定義函數(shù) double read()
	template<typename U = T, typename std::enable_if_t<std::is_same_v<U, double>>* = nullptr>
	U read() {
		return 3.14;
	}
};

類型偏特化:

// T是其它類型
template<typename T, typename = void>
struct zoo;

// 如果T是整型(我從網(wǎng)上扒代碼時(shí)原文這里寫的浮點(diǎn),我覺得應(yīng)該是整型吧,
//這里的std::is_integral_v應(yīng)該是C++新標(biāo)準(zhǔn)里的特性,但也類似于std::is_integral<T>::value,據(jù)說在C++17標(biāo)準(zhǔn)之后了。)
template<typename T>
struct zoo<T, std::enable_if_t<std::is_integral_v<T>>>
{
};

最后查閱到據(jù)說C++ 20中通過concepts又做了一些簡化:

#ifdef __cpp_lib_concepts
#include <concepts>
#endif

// 如果T是整數(shù)類型
template<std::integral T>
void display_concepts_1(T num)
{
}

// 如果T是整數(shù)類型
void display_concepts(std::integral auto num)
{
}

// 如果T是浮點(diǎn)數(shù)類型
void display_concepts(std::floating_point auto num)
{
}

等價(jià)于:

// 如果T是整數(shù)類型
template<typename T, typename std::enable_if_t<std::is_integral_v<T>>* = nullptr>
void display_1(T num)
{
}

// 如果T是整數(shù)類型
template<typename T>
void display(typename std::enable_if_t<std::is_integral_v<T>>* = nullptr)
{
}

// 如果T是浮點(diǎn)數(shù)類型
template<typename T>
void display(typename std::enable_if_t<std::is_floating_point_v<T>>* = nullptr)
{
}

就先寫到這里吧,期待下次遇到有意思的問題繼續(xù)記錄。
參考文章:
[1]: https://blog.csdn.net/jeffasd/article/details/84667090
[2]: https://blog.csdn.net/kpengk/article/details/119979733
[3]: https://www.jianshu.com/p/45a2410d4085

原文鏈接:https://blog.csdn.net/wangx_x/article/details/122867422

欄目分類
最近更新