2012-07-24 79 views
17

我在玩C++ 11的功能特性。我覺得奇怪的一件事是,lambda函數的類型實際上不是函數<>類型。更重要的是,lambda似乎並不能很好地處理類型推入機制。爲什麼在C++ 11中的lambda函數沒有函數<>類型?

附加是一個小例子,我測試了翻轉函數的兩個參數來添加兩個整數。 (我使用的編譯器是MinGW下的gcc 4.6.2。)在此示例中,使用函數<>明確定義了addInt_f的類型,而addInt_l是類型爲auto的類型推理的lambda。

當我編譯的代碼,該flip函數可以接受的明確的類型定義addInt的版本,但不是拉姆達版本,給了一個錯誤說, testCppBind.cpp:15:27: error: no matching function for call to 'flip(<lambda(int, int)>&)'

接下來的幾行表明,拉姆達版本(以及「原始」版本)可以被接受,如果它被明確地轉換爲適當的功能<>類型。

所以我的問題是:

  1. 爲什麼一個lambda函數沒有在首位function<>類型?在這個小例子中,爲什麼addInt_l不是function<int (int,int)>而是類型?從函數式編程的角度來看,函數/函數對象和lambda之間有什麼區別?

  2. 如果有一個根本原因,這兩個必須是不同的。我聽說拉姆達可以轉換爲function<>,但它們不同。這是C++ 11的一個設計問題/缺陷,是一個實現問題,還是在區分兩者的方式方面有利?似乎只有addInt_l的類型簽名提供了有關函數的參數和返回類型的足夠信息。

  3. 有沒有辦法寫lambda,以便上述顯式類型轉換可以避免?

在此先感謝。

//-- testCppBind.cpp -- 
    #include <functional> 
    using namespace std; 
    using namespace std::placeholders; 

    template <typename T1,typename T2, typename T3> 
    function<T3 (T2, T1)> flip(function<T3 (T1, T2)> f) { return bind(f,_2,_1);} 

    function<int (int,int)> addInt_f = [](int a,int b) -> int { return a + b;}; 
    auto addInt_l = [](int a,int b) -> int { return a + b;}; 

    int addInt0(int a, int b) { return a+b;} 

    int main() { 
     auto ff = flip(addInt_f); //ok 
     auto ff1 = flip(addInt_l); //not ok 
     auto ff2 = flip((function<int (int,int)>)addInt_l); //ok 
     auto ff3 = flip((function<int (int,int)>)addInt0); //ok 

     return 0; 
    } 

+4

你不應該服用'std :: function'參數,主要是因爲它禁止類型推導(這是你的問題)。 – 2012-07-24 10:22:02

+1

相關:[C++ 11不會在涉及std :: function或lambda函數時推導出類型](http://stackoverflow.com/q/9998402/487781) – hardmath 2012-07-24 10:32:41

+1

將Lambdas轉換爲匿名函子(或函數,如果它們不要捕捉環境)。將它們轉換爲std :: function將引入語言和庫之間的強大耦合,因此將是一個非常糟糕的主意。 – MFH 2012-07-24 10:33:37

回答

39

std::function商店任何種類的可調用對象不管其類型的有用的工具。爲了做到這一點,它需要採用某種類型的擦除技術,並且涉及一些開銷。

任何callable可以隱式轉換爲std::function,這就是爲什麼它通常無縫工作。

我會重複以確保它變得清晰:std::function不僅僅適用於lambdas或函數指針:它適用於任何類型的可調用函數。例如,這包括諸如struct some_callable { void operator()() {} };之類的內容。這是一個簡單的,但它可能是這樣的,而不是:

struct some_polymorphic_callable { 
    template <typename T> 
    void operator()(T); 
}; 

一個lambda只是又一個可調用對象,類似於上面的some_callable對象的實例。它可以存儲在std::function中,因爲它是可調用的,但它不具有類型擦除開銷std::function

而且該委員會計劃在將來製作多形態lambda,即上面看起來像some_polymorphic_callable的lambda。哪個std::function類型會這樣lambda是?


現在...模板參數扣除或隱式轉換。選一個。這是C++模板的一個規則。

要傳遞一個lambda作爲std::function參數,它需要隱式轉換。採用std::function參數意味着您選擇的是類型演繹的隱式轉換。但是您的函數模板需要明確推導或提供簽名。

解決方案?不要限制您的呼叫者爲std::function。接受任何一種可回收

template <typename Fun> 
auto flip(Fun&& f) -> decltype(std::bind(std::forward<Fun>(f),_2,_1)) 
{ return std::bind(std::forward<Fun>(f),_2,_1); } 

您現在可以想爲什麼我們需要std::function然後。 std::function爲具有已知簽名的可調品提供了類型擦除。這基本上可以用來存儲可擦除類型的可調對象並編寫接口。

+0

:謝謝你的明確解釋。恕我直言,std :: function只能用於存儲的事實相當讓人失望。如果我正確理解你的答案,這是爲了避免開銷?那麼,智能指針也一樣嗎?另外,在你的'some_polymorphic_callable'例子中,我不明白爲什麼函數<>類型不能表達模板化lambda的類型,因爲函數<>可以有模板參數。如果開銷是唯一的問題,有沒有人認爲輕量級函數<>可以用於參數類型?這將使STL更加可用。 – tinlyx 2012-07-26 00:00:37

+0

此外,在'decltype'示例中,如果函數體是25行代碼而不是單行內容,那麼我們是否需要在decltype中包含25行?另外,使用''看起來幾乎像void *或宏。只有在發生編譯錯誤時才能檢測到「Fun」的錯誤使用。相比之下,函數<>參數類型(如果可用)將清楚地告知個人和計算機類型簽名。無可否認,我對「類型擦除」等知識非常有限。感謝您告訴我規則。我只是想知道爲什麼它必須這樣。 – tinlyx 2012-07-26 00:14:00

+0

@TingL多態lambda表達式的類型不能表達,因爲'function''需要*一個*簽名參數,而多態lambda表達式會有*無限數量的不同簽名*(這就是爲什麼它稱爲多態:它有很多形式)。對於智能指針,開銷問題並不完全相同。 'unique_ptr'具有真正的*零*開銷(這是設計目標之一)。如果你沒有運行多線程程序,'shared_ptr'可能會有一些開銷,但是多線程程序更可能被使用。 – 2012-07-26 00:47:14

5
  1. 由於function<>採用type erasure。這允許將幾個不同的功能類型類型存儲在function<>中,但是會導致運行時間較短。類型擦除隱藏虛擬功能接口後面的實際類型(特定的lambda)。
  2. 這樣做是有好處的:C++設計「公理」之一是除非確實需要,否則不會增加開銷。使用此設置時,使用類型推斷(使用auto或作爲模板參數傳遞)時不會有任何開銷,但您仍然可以靈活地通過function<>與非模板代碼進行交互。還請注意,function<>不是一種語言結構,而是可以使用簡單語言功能實現的標準庫的一個組件。
  3. 不,但您可以編寫該函數以僅使用函數(語言結構)的類型而不是function<>(庫結構)的細節。當然,這使得實際寫下返回類型變得困難很多,因爲它不直接給你提供參數類型。但是,使用一些元編程a la Boost.FunctionTypes,您可以從您傳入的函數中推導出這些函數。有些情況下這是不可能的,例如,仿函數的模板爲operator()
相關問題