C++11 所推出的 Lambda expression 這種匿名函式,Heresy 一開始還覺得不知道哪裡可以用?但是後來真正寫多了,才發現他實際上是非常好用的一個功能。
而在 C++14 / C++17,也又針對 lambda 加入了一些新的功能、讓他更為強大。
不管是 captures with an initialiser 還是 generic lambda,都讓 lambda 的使用上更為方便。
到了 C++20,則是又加入了「template parameter for lambda」這項特性,算是又做了一些強化。
實際上,這部分也滿早就有人在介紹了,不過當時由於主力的編譯器還不支援,所以 Heresy 就只有寫到 C++17 的部分;最近由於在寫的程式剛好用得上,所以就來追加寫一下吧。
首先,Template lambda 長怎樣呢?它的形式基本上是:
[]<>(){}
看起來很有趣吧? XD
其中,紅色的 <> 就是 C++20 追加的 template parameter 的部分;不過,這邊也提醒一下,上面的程式碼其實並不完整,是不能編譯的。
實際上要寫的話,他大概會是下面的樣子:
[]<typename T>(T v1, T v2) {/*C++20 template lambda*/}
其實和一般的 template function 形式很接近。透過這樣的寫法,就可以讓這個 lambda 適用於各種型別了~
不過實際上,在 C++14 已經有很類似的功能的 generic lambda 了,大概會長得像下面的樣子:
[](auto v1, auto v2) {/*C++14 generic lambda*/}
這兩者差別在哪呢?
在個人來看,template lambda 的重點,應該是他可以寫得更嚴謹。
這邊可以參考《More Powerful Lambdas with C++20》所給的例子:
#include <iostream> #include <string> #include <vector>
// only to int convertible types (C++11)
auto sumInt = [](int fir, int sec) { return fir + sec; };
// arbitrary types (C++14)
auto sumGen = [](auto fir, auto sec) { return fir + sec; }; // arbitrary, but convertible types (C++14)
auto sumDec = [](auto fir, decltype(fir) sec) { return fir + sec; }; // arbitrary, but identical types (C++20)
auto sumTem = []<typename T>(T fir, T sec) { return fir + sec; }; int main() {
std::cout << std::endl; // (1)
std::cout << "sumInt(2000, 11): " << sumInt(2000, 11) << std::endl;
std::cout << "sumGen(2000, 11): " << sumGen(2000, 11) << std::endl;
std::cout << "sumDec(2000, 11): " << sumDec(2000, 11) << std::endl;
std::cout << "sumTem(2000, 11): " << sumTem(2000, 11) << std::endl;
std::cout << std::endl; // (2)
std::string hello = "Hello ";
std::string world = "world";
// std::cout << "sumInt(hello, world): " << sumInt(hello, world) << std::endl; ERROR
std::cout << "sumGen(hello, world): " << sumGen(hello, world) << std::endl;
std::cout << "sumDec(hello, world): " << sumDec(hello, world) << std::endl;
std::cout << "sumTem(hello, world): " << sumTem(hello, world) << std::endl;
std::cout << std::endl; // (3)
std::cout << "sumInt(true, 2010): " << sumInt(true, 2010) << std::endl;
std::cout << "sumGen(true, 2010): " << sumGen(true, 2010) << std::endl;
std::cout << "sumDec(true, 2010): " << sumDec(true, 2010) << std::endl;
// std::cout << "sumTem(true, 2010): " << sumTem(true, 2010) << std::endl; ERROR
std::cout << std::endl; }
在上面的例子裡面,總共有四個相加的 lambda,分別是:
- sumInt
- 應該算是最標準的 C++11 lambda 寫法。
- 可以接受任何可以轉換成 int 的引數。
- sumGen
- C++14 的 generic lambda
- 可以接受任何型別的引數,只要兩者可以相加就沒問題
- sumDec
- 一樣是 C++14 的 generic lambda,不過針對第二個引數的型別做限制
- 第二個參數需要可以轉換成第一個引數的型別(decltype(fir))
- sumTem
- C++ 20 的 template lambda
- 兩個引數的型別需要完全一致
以一個更簡單的測試來看,就是下面的樣子:
int v1 = 1; float v2 = 1.0f; std::cout << sumInt(v1, v2) << std::endl; std::cout << sumGen(v1, v2) << std::endl; std::cout << sumDec(v1, v2) << std::endl; // std::cout << sumTem(v1, v2) << std::endl; ERROR
在一個引數是 int、一個是 float 的情況下,僅有要求最嚴格的 sumTem 是無法編譯的。
當然,除了直接這樣使用外,他也還是可以像一般的 template function 一樣,用在其他 template class 上。
[]<typename T>(std::vector<T> v) {/*...*/ }
比如說,上面的寫法就可以限制只能使用 std::vector 這樣的容器作為引數了。
而如果搭配 C++20 的 concepts 的話,則也可以進一步做型別的限制。例如:
[]<std::integral T>(T v) {/*...*/ }
這樣就可以限制引數一定要是整數型態的型別,其他的都不行了。
這些限制都是 generic lambda 難以辦到的。
但是相對地,如果不在乎要附加這些條件的話,其實 generic lambda 也都是可以用的。 XD
而個人覺得比較可惜的,是雖然 template lambda 似乎沒辦法和一般的 template function 一樣,在執行的時候直接指定 template 參數?
template<typename T> void func() { /*...*/} int main() { func<int>(); auto fLambda = []<typename T>() {/*...*/ }; fLambda<int>(); //ERROR }
在上面的例子裡,一般的 template function func 可以直接透過 func<int>(); 的形式來呼叫沒問題,但是 template lambda 的 fLambda 就不行了…
如果真的想要這樣玩也不是不行,不過就是得透過他內部產生的 operator() 了:
fLambda.operator()<int>();
這樣寫其實就可以正常運作了。不過相對地,可能在使用上就沒那麼好用了…
參考: