關於C++中在模板引數中使用Lambda表示式的問題

抓水母的派大星發表於2024-03-21

問題來源

直接在模板引數中使用lambda表示式不被允許。比如: priority_queue<int, vector<int>, greater<int>> minHeap; 在最小堆定義中,我們第三個模版是 greater<int>,這個模版引數希望我們傳入一個型別,而不是函式,因為模版引數在編譯時就確定其型別,所以不可以直接使用函式。也就不能和普通的 bool compare(int x, int y) {} 這樣子寫然後傳入(因為這是一個普通函式)。

所以,模版引數我們可以使用 函式物件使用lambda表示式搭配decltype推導函式表示式 的方法,這樣子他們的返回值是允許在模版引數中被傳入的。(簡單說,如果需要實現一個自定義的比較邏輯,應該使用一個函式物件(functor)或 lambda表示式(配合decltype)來作為模板引數。

使用如下:

  1. 返回函式物件:
struct Compare {
    bool operator()(const int& a, const int& b) const {
        // 自定義比較邏輯
        return a > b; // 例如,實現最小堆 (也可以直接用priority_queue<int, vector<int>, greater<int>> minHeap,這裡只是展示一下怎麼返回函式物件)
    }
};

std::priority_queue<int, std::vector<int>, Compare> minHeap;
  1. lambda表示式(配合decltype):
// 定義lambda表示式作為比較函式
auto comp = [](const int& left, const int& right) 
{ 
    return left > right; 
};
    
// 使用 decltype 來推斷 lambda 表示式的型別
priority_queue<int, vector<int>, decltype(comp)> minHeap(comp);

原因:

  • C++的設計強調 型別安全在編譯時確定型別資訊,因為這會提高程式的效能和型別安全。
  • 直接使用lambda可能會導致難以診斷的錯誤和理解上的困難,因為lambda表示式的隱式型別不容易在錯誤訊息中明確表示。
  • 模板引數的型別:C++中,模板引數需要在編譯時就確定其型別。對於型別模板引數(如類别範本或函式模板的型別引數),這意味著必須使用具體的型別名稱。而lambda表示式在C++中是一個匿名函式物件,每個lambda表示式的型別是唯一且不可重複的,並且這個型別是由編譯器在編譯時自動生成的。
  • Lambda表示式的型別:由於lambda表示式是匿名的,它們沒有顯式的型別名,所以直接在模板引數中引用lambda表示式的型別是不行的。每個lambda表示式的型別雖然可以透過decltype運算子來推導,但在模板引數中直接寫一個lambda表示式並沒有提供一個型別,而是嘗試提供一個具體的物件例項,這與模板引數期望的型別資訊不匹配。

解決辦法:

  1. 使用自定義函式
  2. 使用 Lambda函式,但是需要和 decltype 結合(推導表示式型別)。

模版中使用自定義函式

當我們需要使用優先佇列定義一個小根堆,這樣子使用就可以了 priority_queue<int, vector<int>, greater<int>> minHeap; ,裡面使用了 vector 存放了int 型別的資料。

但是如果內部元素變成了連結串列(指標域+資料域),並且我們需要對連結串列節點進行排序的時候,我們就不能用自帶的greater比較器了。 但是我們可以透過自定義函式+函式過載寫成如下:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

struct compare {
    bool operator()(const ListNode* l, const ListNode* r) {
        return l->val > r->val;
    }
};

class Solution {
public:
    ListNode* mergeKLists(std::vector<ListNode*>& lists) {
        std::priority_queue<ListNode*, std::vector<ListNode*>, compare> pq;
...

模版中使用Lambda

問題轉化為 -> 使用lambda表示式作為priority_queue比較器

如果我們不使用自定義比較函式,而是使用Lambda表示式,那麼需要透過一個lambda表示式初始化一個函式物件,並傳遞這個物件作為 std::priority_queue 的構造引數。然而,直接在模板引數中使用lambda表示式是不允許的,因為lambda表示式的型別是在編譯時自動生成的,每個lambda表示式都有其獨特的型別。

為了實現這一點,可以使用 std::function 包裝比較lambda表示式,但是注意 std::priority_queue 的模板引數需要一個型別,而不是一個物件。因此,一個常見的做法是定義一個函式物件型別,可以透過建立一個結構體或類,其中包含一個呼叫運算子(())來實現這一點。但是,對於直接在 priority_queue 模板引數中使用lambda表示式,通常採取另一種方法:使用 decltype 關鍵字推斷lambda表示式的型別,並結合使用 auto 關鍵字來宣告比較器物件。

基本使用:

auto compare = [](const ListNode* l, const ListNode* r) { return l->val > r->val; };
priority_queue<ListNode*, vector<ListNode*>, decltype(compare)> miniHeap(compare);

詳細使用如下:

#include <iostream>
#include <queue>
#include <vector>

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

int main() {
    auto compare = [](const ListNode* lhs, const ListNode* rhs) {
        return lhs->val > rhs->val;
    };

    // 使用 decltype(comp) 作為 priority_queue 的比較型別
    priority_queue<ListNode*, std::vector<ListNode*>, decltype(compare)> minHeap(compare);
    ....

    return 0;
}

相關文章