2020年5月28日

[C++] Compile Time if in C++17

要說的話...就是把原本要用 SFINAE (substitution failure is not an error) 才能做到的事情變成可以用更直覺的寫法來做到。
這個我們直接看例子比較快:
template <bool is_numeric> struct AsString;

template<>
struct AsString<true> {
	template <typename T>
    static string toStr(T x) { return std::to_string(x); }
};

template<>
struct AsString<false> {
	template <typename T>
    static string toStr(T x) { return std::string(x); }
};

int main() {
	const auto val = 5;
	const auto str = "hello world";
	cout << AsString<is_arithmetic<decltype(val)>::value>::toStr(val) << '\n'
	     << AsString<is_arithmetic<decltype(str)>::value>::toStr(str) << '\n';
	return 0;
}
上面這個例子是在 C++17 前如果想要把任意型態的 (雖然還不夠完整,以這邊的例子來說只有數值型態以及字串型別) 的值轉成 std::string 的寫法。這邊的做法是利用 SFINAE 搭配 type straits 的方式來達到在 compile time 就能確定要用哪種方式把值轉成 std::string 的型態。關於 type traits 搭配 SFINAE 的作法可以參考這篇,這裡就不多做解釋。

以上寫法的優點雖然是能在 compile time 就能確定要用哪種方式把值轉成 std::string 避免 compiler 產生無謂的 code 以及增加 runtime overhead,缺點也很明顯:就是那個邏輯其實很難懂,再加上 template 本身不易解讀又有各種限制 (以這邊的例子來說,function 不能有 partial specialization,所以寫起來會更複雜一點),相對的也會增加維護上的難度,是以 C++17 引入的 compile time if 就是為了簡化這種狀況的。

一樣看個例子
template <typename T>
std::string toString(T x)
{
	if constexpr (is_arithmetic<T>::value) {
		return std::to_string(x);
	}
	else {
		return std::string(x);
	}
}

int main() {
	const auto val = 5;
	const auto str = "hello world";
	cout << toString(val) << '\n'
	     << toString(str) << '\n';
	return 0;
}
前後比對後應該不難發現真的簡化易懂許多

特別值得注意的是,上面的寫法如果不是 compile time if 而是 runtime if (也就是一般 if 的寫法),則這個 sample code 會遇上 compile error,因為 std::to_string 的參數不接受 非數值型態的參數、std::string 的 constructor 則是不接受 string 以外的參數。

compile time if 能用的原因其實就跟 SFINAE 有異曲同工之妙:條件不符的區塊甚至不會進行編譯,所以在寫法簡化的同時依然能達到以往寫法的優點。不過這點有一個例外:就是 static_assert。舉例來說,下面這例子編譯一定失敗:
template 
std::string toString(T x)
{
	if constexpr (is_arithmetic::value) {
		static_assert(false, "always fail");
		return std::to_string(x);
	}
	else {
		return std::string(x);
	}
}
就算 T 的型別不是數值型態,static_assert 依然會被觸發,這是跟 SFINAE 寫法的第一個不同之處。

第二點則是 compile time if 沒有 short circuit evaluation,所以寫在 if condition 內的所有算是都會被運算確認,換言之,只要有一個條件會導致編譯錯誤就不行

第三點跟 if 的特性有點像:如果今天有多個 if 的條件都能符合,則 compiler 會選第一個符合的區塊;相對的,以往 SFINAE 的寫法因為會利用 type deduction,所以是挑選型態符合程度最高的。也因此在有多個 specialization 寫法都能符合條件的情況下,SFINAE 的寫法跟 compile time if 的結果是有可能不同的。

簡單來說,這是個提供更高便利性及可讀性的新語法,不過如果是本來就不常用 template 的人我想大概對這個新語法無感。

沒有留言:

張貼留言