C++20 的 template lambda

| | 0 Comments| 16:52
Categories:

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>();

這樣寫其實就可以正常運作了。不過相對地,可能在使用上就沒那麼好用了…


參考:

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *