2014-02-05 81 views
12

我實現了一個通用事件發射器類,它允許代碼註冊回調函數,並使用參數發送事件。我使用Boost.Any類型的擦除來存儲回調,以便他們可以有任意的參數簽名。爲什麼C++ 11不會將lambdas轉換爲std :: function對象?

這一切都有效,但由於某種原因,傳入的lambda必須首先變爲std::function對象。爲什麼編譯器不推斷lambda是函數類型?是因爲我使用可變模板的方式嗎?

我用Clang(版本字符串:Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn))。

代碼:

#include <functional> 
#include <iostream> 
#include <map> 
#include <string> 
#include <vector> 

#include <boost/any.hpp> 


using std::cout; 
using std::endl; 
using std::function; 
using std::map; 
using std::string; 
using std::vector; 


class emitter { 

    public: 

     template <typename... Args> 
     void on(string const& event_type, function<void (Args...)> const& f) { 
     _listeners[event_type].push_back(f); 
     } 

     template <typename... Args> 
     void emit(string const& event_type, Args... args) { 
     auto listeners = _listeners.find(event_type); 
     for (auto l : listeners->second) { 
      auto lf = boost::any_cast<function<void (Args...)>>(l); 
      lf(args...); 
     } 
     } 

    private: 

     map<string, vector<boost::any>> _listeners; 

}; 


int main(int argc, char** argv) { 

    emitter e; 

    int capture = 6; 

    // Not sure why Clang (at least) can't deduce the type of the lambda. I don't 
    // think the explicit function<...> business should be necessary. 
    e.on("my event", 
     function<void()>(// <--- why is this necessary? 
      [&]() { 
       cout << "my event occurred " << capture << endl; 
      })); 
    e.on("my event 2", 
     function<void (int)>(
      [&] (int x) { 
       cout << "my event 2 occurred: " << x << endl; 
      })); 
    e.on("my event 3", 
     function<void (double)>(
      [&] (double x) { 
       cout << "my event 3 occurred: " << x << endl; 
      })); 
    e.on("my event 4", 
     function<void (int, double)>(
      [&] (int x, double y) { 
       cout << "my event 4 occurred: " << x << " " << y << endl; 
      })); 

    e.emit("my event"); 
    e.emit("my event 2", 1); 
    e.emit("my event 3", 3.14159); 
    e.emit("my event 4", 10, 3.14159); 

    return EXIT_SUCCESS; 
} 

回答

10

拉姆達不是std::function,而std::function不是拉姆達。

了lambda語法糖來創建一個匿名類,它看起來是這樣的:

struct my_lambda { 
private: 
    int captured_int; 
    double captured_double; 
    char& referenced_char; 
public: 
    int operator()(float passed_float) const { 
    // code 
    } 
}; 
int captured_int = 7; 
double captured_double = 3.14; 
char referenced_char = 'a'; 
my_lambda closure {captured_int, captured_double, referenced_char}; 
closure(2.7f); 

從這個:

int captured_int = 7; 
double captured_double = 3.14; 
char referenced_char = 'a'; 
auto closure = [=,&referenced_char](float passed_float)->int { 
    // code 
}; 
closure(2.7); 

my_lambda實際上是一些難以名狀的類型的類型名稱。

A std::function是完全不同的東西。它是一個實現operator()並具有特定簽名的對象,並將智能值語義指針存儲到覆蓋複製/移動/調用操作的抽象接口。它有一個template d構造函數,它可以採用任何類型來支持具有兼容簽名的copy/move/operator(),生成一個實現抽象內部接口的具體自定義類,並將其存儲在上述內部值語義智能指針中。

然後,它將作爲值類型的操作從它自身轉發到抽象內部指針,包括完美轉發到調用方法。

碰巧,你可以std::function中存儲一個lambda,就像你可以存儲一個函數指針一樣。

但也有不同std::function的整體無數,可以存儲一個給定的λ - 任何在類型轉換和從變量工作,實際上的效果一樣好,只要std::function關注。

在C++中的類型扣減template s不能在「可以轉換成」的級別工作 - 它是模式匹配,純粹而簡單。由於拉姆達是與std::function無關的類型,因此不能從中推導出std::function類型。

如果C++試圖在一般情況下這樣做,它將不得不顛倒Turing-complete過程來確定哪些類型(如果有的話)可以傳遞給template以便生成兼容轉換的實例。從理論上講,我們可以在語言中加入「運算符演繹模板參數」,其中給定template的實現者可以編寫一些任意類型的代碼,並且他們試圖從這種類型中梳理出什麼template參數應該用於一個實例「。但是C++沒有這個。

+0

它看起來像C++有任何其他語言沒有的問題。在Haskell,C#中,Python有一個lambda類型,不需要任何顯式轉換。 – Trismegistos

+1

@trismegistos是的,這些語言默認情況下都會刪除lambda表達式。 C++ lambda的設計與編寫合理優化的C級代碼一樣快,但傳遞給算法時更容易,這意味着lambda的類型信息不會被丟棄,因此使用它們的算法被複制並且對於每個lambda都是不同的通過,這使內聯微不足道。 – Yakk

+0

是否意味着當我傳遞兩次創建相同的lamdba並在兩個lambdas上調用std :: find_if時,我將會編譯兩個不同的find_if函數,它們是二進制相同的?這聽起來不像是很好的優化。 – Trismegistos

6

因爲編譯器實現了C++語言編譯器不會推斷出任何東西,並且語言的模板實參推演規則不允許你想要的方式演繹。

這裏有一個簡單的例子,表示您的情況:

template <typename T> struct Foo 
{ 
    Foo(int) {} 
}; 

template <typename T> void magic(Foo<T> const &); 

int main() 
{ 
    magic(10); // what is T? 
} 
+0

我認爲這解釋了爲什麼你不能直接將lambda傳遞給採用模板化函數的方法,但我認爲這裏的基本問題與'any'類型推導規則相比'std: :function'。還是我誤會了? – templatetypedef

+0

在我看來,編譯器應該推斷T是一個整型,因爲這些信息在編譯時顯然是存在的。你是說這只是沒有,這是結束了嗎? – gcv

+5

@gcv爲什麼不'Foo '?或'Foo '?每個可能的'Foo'實例都有一個來自'int'的轉換構造函數。 – Casey

1

boost::any存儲的值,它使用靜態類型的對象,以確定被存儲什麼類型的對象。如果您指定存儲內容的靜態類型,則只能將any轉換回正確類型的對象。

每個C++ lambda都與一個對用戶不透明的實現定義類型相關聯。雖然lambda可以稱爲函數,但它們不直接評估爲std::function。當存儲在any中的lambda以確保存儲的靜態類型是std::function時,您需要投射。

希望這會有所幫助!

相關問題