2018年5月22日

[C++] fold expression

fold expression 是 C++17 中專為 variadic template (或者說 parameter pack) 設計的東西。

簡單來說 parameter pack 允許在 template 使用不定數量與型態的 template arguments,語法上其實就像是傳統的 variable length parameter (像是 printf 那種) 那樣。詳細的語法介紹這邊先跳過,先回過頭專注在 fold expression 這鬼東西上。

先參考下面這個例子:
#include <iostream>

template <typename ...Args>
void printer(Args&& ...args)
{
    (std::cout << ... << args) << '\n';
}

template <typename T, typename ...Args>
T sum_all(Args&& ...args)
{
    return (args + ...);
}

int main()
{
    printer(1, 2, 3, "xyz", 3.5, 'a');
    std::cout << sum_all<int>(2, 2, 3, 4, 5) << '\n';
    return 0;
}

printer 跟 sum_all 這兩個 function 的參數就是 parameter pack 的寫法。我們在 main function 可以發現傳入的參數型態與數量基本上是隨意的,compiler 自然會在 compile time 做型態推導。parameter pack 實際使用上的麻煩通常會在於你想要拿到個別的參數做運算處理。像是這邊的 printer 或是 sum_all,一個是要把每個參數都印出來;另一個是要把所以參數通通加總。

fold expression 簡單來說是為了這種情況設計的語法,你可以把 parameter pack 中的所有參數都套用相同的運算子。像是 printer 這個 function 就是把所有的參數都丟到 std::cout 去;同樣的,sum_all 這個 function 則把所有參數都套用 + 這個運算子。因此前者的結果就是 std::cout 會輸出所有的參數;而後者則是把所有參數用 + 這個運算子全部串連起來運算後得到最終結果 (所以就是加總所有參數囉)。

這邊也列一下如果沒有用 fold expression 的話程式碼大概會長成怎樣才會有同樣的效果
#include <iostream>

template <typename T>
void printer(T&& args)
{
    std::cout << args;
}

template <typename ...Args>
void printer(Args&& ...args)
{
    std::initializer_list<int>{ (printer(args), 0) ... };
    std::cout << '\n';
}

template <typename T>
int sum_all(T&& args)
{
    return args;
}

template <typename T, typename ...Args>
int sum_all(T&& t, Args&& ...args)
{
    return (t + sum_all(args...));
}

int main()
{
    printer(1, 2, 3, "xyz", 3.5, 'a');
    std::cout << sum_all(1, 2, 3, 4, 5) << '\n';
    return 0;
}

簡單來說就是需要多一個 overload function 以便讓 compiler 在 parameter unpack 時可以針對只有一個參數的情況去運作。而 unpacking 的方法則是可以利用 std::initializer_list 或是遞迴去運作。這樣比較後應該不難發現 fold expression 可以寫出較為精簡的程式碼。

當然 fold expression 也不是沒有缺點,因為它會直接要求所有參數都要套用相同的運算子,如果今天你想針對不同型態有不同運作方式時,那就不能用 fold expression 了。另一方面,fold expression 在 init expression 也有一些的語法限制 ,所以寫起來相對比較沒有彈性 (比方說我在 printer 這個 function 我還試不出有甚麼方法可以利用 fold expression 又能用讓兩兩參數中間印出空格區隔開來...)

總結來說,確實是個有用處的語法,但要說他是否實用...這我倒是持保留態度,因為能派上用場、而且用了比沒用好的情況我覺得並不多就是。


Reference:
http://en.cppreference.com/w/cpp/language/fold

沒有留言:

張貼留言