C++模板之SFINAE和enable_if分析

C++模板之SFINAE和enable_if分析

C++ type traits分析 這篇文章我,我們講述了type traits的實現用法和基本原理。

本文我們討論一下兩個問題:

  1. 如果實現is_class的type traits。
  2. 怎麼樣禁止函數編譯期間根據特定的條件來選擇啓用或禁用特定的重載。

1. SFINAE

SFINAE是"Substitution failure is not an error"(替換失敗不是錯誤),官方給的解釋爲:在函數模板的重載決議中:爲模板形參替換推導類型失敗時,從重載集拋棄特化,而非導致編譯錯誤

用一句簡單的話來說,C++在編譯的時候,模板實例化的時候就算實例化失敗也不會出現錯誤。

例如我們用一個簡單的例子來描述:

template<typename T>
void test(typename T::noexist t)
{

}

void test(int t)
{

}

int main(int args, char* argv[])
{
	test(100.1);
	return 0;
}

我們在使用test(100.1)實例化模板test的時候,並不會編譯錯誤,只是會實例化失敗,使用void test(int t)這個函數。

所以SFINAE 的含義是:編譯器在將函數模板形參替換成實參的過程中,如果針對某個函數模板產生了不合法的代碼,其不會直接拋出錯誤信息,而是繼續嘗試去匹配其他可能的重載函數

What… 這是什麼鬼? 不是本來C++特性就是這樣嘛?有必要講的那麼高大上嘛?不急,我們使用這個特性看一下SFINAE可以做什麼事情。

2. isClass的實現

namespace SFINAE
{
	template<typename T>
	class IsClass
	{
	private:
		template<typename U>
		static constexpr bool is_class(int U::*)
		{
			return true;
		}
		template<typename U>
		static constexpr bool is_class(...)
		{
			return false;
		}

	public:
		static constexpr bool value = is_class<T>(nullptr);
	};
}

class A
{

};

class B
{
	int a;
	int b;
};
int main(int args, char* argv[])
{
	std::cout << "A is class: " << SFINAE::IsClass<A>::value << std::endl;
	std::cout << "B is class: " << SFINAE::IsClass<B>::value << std::endl;
	std::cout << "int is class: " << SFINAE::IsClass<int>::value << std::endl;
	std::cout << "double is class: " << SFINAE::IsClass<double>::value << std::endl;
	return 0;
}

這裏有兩個技巧:

  1. int U::* 使用這個來模板實例化,如果是類,可以定義類的成員指針,這個最佳匹配。
  2. is_class(...) 如果不是類,那麼這個函數適配所有的類型。

這個的運行結構爲:

A is class: 1
B is class: 1
int is class: 0
double is class: 0

這個就是利用模板實例化的時候,如果第一個實例化不了,會繼續實例化其他而不會出錯的特性。

3. enable_if

enable_if解決的一個問題是:如何在函數編譯期間根據特定的條件來選擇啓用或禁用模板實例化。加入我們存在一個Add加法操作,但是我們限定一個東西,就是Add只能使用整數,像std::string不能適配,我們可以使用如下:

//整數
template<typename T, 
	typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
auto Add(T a, T b) -> T
{
	return a + b;
}

int main(int args, char* argv[])
{
	std::string str = "abc";
	Add(100, 200);
	str + "abc";
	Add(str, str);
	return 0;
}

此時編譯的時候就會出錯:

: error C2672: 'Add': no matching overloaded function found
: error C2783: 'T Add(T,T)': could not deduce template argument for '__formal'
: note: see declaration of 'Add'

也就是說在模板實例化的時候並沒有成功。enable_if 的主要作用就是當某個 condition 成立時,enable_if可以提供某種類型。

我們看下enable_if的實現原理。

template<bool _Test,
	class _Ty = void>
	struct enable_if
	{	// type is undefined for assumed !_Test
	};

template<class _Ty>
	struct enable_if<true, _Ty>
	{	// type is _Ty for _Test
	using type = _Ty;
	};

也就是說,默認情況下enable_if是一個空的類定義,如果bool _Test 參數爲TRUE的話,使用片特例模板struct enable_if<true, _Ty>,這個模板可以定義type類型。例如:

class A
{
public:
	int a;
};
int main(int args, char* argv[])
{
	std::enable_if<std::is_class<A>::value, A>::type a;
	a.a = 100;
	return 0;
}

C++庫還定義瞭如下類型:

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

類似的東西在C++標準庫中使用很多,例如std::vector,如下:

template<class _Iter,
		class = enable_if_t<_Is_iterator_v<_Iter>>>
		vector(_Iter _First, _Iter _Last, const _Alloc& _Al = _Alloc())
		: _Mybase(_Al)
		{	// construct from [_First, _Last) with optional allocator
		_Adl_verify_range(_First, _Last);
		_Range_construct_or_tidy(_Get_unwrapped(_First), _Get_unwrapped(_Last), _Iter_cat_t<_Iter>{});
		}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章