2018年8月9日

[C++] Compile-Time if in C++17 Can Have Code Not Able to Be Compiled with Regular Runtime if

Compile-time if 在 C++17 中也算是一個滿有趣的特色,其特點就在於能夠在 compile time 就幫你確認清楚 if statement 中會執行到哪一個判斷式對應到的區塊,從而把其他一定不會用到的區塊 "拔掉"。這點在現在電腦上算是個滿有用的特色,畢竟現在 CPU 的 branch prediction 一定錯誤通常都要承擔不小的後果 (註) 

原本我以為 compile-time if 只是針對一些能在 compile-time 就能推知結果的運算做優化 (特別是在 template 上),結果今天看到一個例子才發現這玩意兒比想像中更猛,甚至能夠寫出一些原本 regular runtime if 做不到的功能。

以下例子來自於 C++17 - The Complete Guide

下面這段是使用 compile-time if 的寫法
template <typename T>
std::string toString_constexpr_if(T x)
{
 if constexpr(std::is_same_v<T, std::string>) {
  return x; // statement invalid, if no conversion to string
 }
 else if constexpr(std::is_arithmetic_v<T>) {
  return std::to_string(x); // statement invalid, if x is not numeric
 }
 else {
  return std::string(x); // statement invalid, if no conversion to string
 }
}

if constexpr 就是 compile-time if 的寫法,這段 code 的結果會是
  1. 如果 T 是 std::string:直接回傳參數本身
  2. 如果 T 是數字:利用 std::to_string 轉成 std::string 的 object
  3. 其他:利用 std::string 的 constructor 轉成 std::string 的 object
compile-time if 的特色在於假設 1. 成立,那 2、3 的 code 就會變成非法然後拿掉。換言之產生的執行檔就不會有 2. 跟 3. 那兩段程式碼。在 C++17 前類似的需求必須要靠 SFINAE 來達成,雖然並非做不到,但是程式碼會因此複雜難解許多。

另一個值得注意的特點在於這段 code 是不可能用 regular runtime-if 做到的,比方說下面的例子:
template <typename T>
std::string toString_regular_if(T x)
{
 if (std::is_same_v<T, std::string>) {
  return x;
 }
 else if (std::is_arithmetic_v<T>) {
  return std::to_string(x);
 }
 else {
  return std::string(x);
 }
}

雖然僅僅只是拿掉 if 後面的 constexpr 變成一般的 if-else statement,但是這段程式碼是無法編譯的,同樣的三種情形:
  1. 如果 T 是 std::string:std::to_string(x) 會無法編譯 (因為 overloading function 中沒有參數型態是 std::string),即便你知道不會走到那一行
  2. 如果 T 是數字:return x; 以及 std::string(x) 都會無法編譯
  3. 其他:x 是數字型態就會編譯失敗

以上就是 compile-time if 的另一大特點。挺有趣的,不過實際使用上其實跟 template會掛在一起,實用與否挺看人的,不常用 template 的人應該會很無感 XD


完整例子請參考:
https://gist.github.com/shininglion/6e4d1149f43439c3836674a94a1a3441


註:
你看看 Intel 對 branch prediction 做優化搞出了個超大的資安漏洞...就知道這玩意兒是多麻煩的東西...

沒有留言:

張貼留言