2016-06-09 41 views
10

考慮下面的代碼:複製使用默認參數的lambda函數的變量

#include <iostream> 
#include <functional> 
using namespace std; 

int main() { 
    auto f = [](int a = 3) {cout << a << endl; }; 
    f(2); // OK 
    f(); // OK 

    auto g = f; 
    g(2); // OK 
    g(); // OK 

    function<void(int)> h = f; 
    h(2); // OK 
    h(); // Error! How to make this work? 

    return 0; 
} 

我怎樣才能申報h有同樣的表現爲fg

+0

'auto h = f;'? – MikeCAT

+0

我不認爲'std :: function'有任何默認參數支持。 – chris

+0

@MikeCAT我知道:)如果我想完全指定類型,是否有正確的方法是什麼? –

回答

12

std::function有一個固定的簽名。這是一個設計選擇,不是一個硬性要求。編寫支持多個簽名僞std::function並不難:

template<class...Sigs> 
struct functions; 

template<> 
struct functions<> { 
    functions()=default; 
    functions(functions const&)=default; 
    functions(functions&&)=default; 
    functions& operator=(functions const&)=default; 
    functions& operator=(functions&&)=default; 
private: 
    struct never_t {private:never_t(){};}; 
public: 
    void operator()(never_t)const =delete; 

    template<class F, 
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr 
    > 
    functions(F&&) {} 
}; 

template<class S0, class...Sigs> 
struct functions<S0, Sigs...>: 
    std::function<S0>, 
    functions<Sigs...> 
{ 
    functions()=default; 
    functions(functions const&)=default; 
    functions(functions&&)=default; 
    functions& operator=(functions const&)=default; 
    functions& operator=(functions&&)=default; 
    using std::function<S0>::operator(); 
    using functions<Sigs...>::operator(); 
    template<class F, 
    std::enable_if_t<!std::is_same<std::decay_t<F>, functions>{}, int>* =nullptr 
    > 
    functions(F&& f): 
    std::function<S0>(f), 
    functions<Sigs...>(std::forward<F>(f)) 
    {} 
}; 

使用:

auto f = [](int a = 3) {std::cout << a << std::endl; }; 

functions<void(int), void()> fs = f; 
fs(); 
fs(3); 

Live example

這將爲每個過載創建一個單獨的lambda複製副本。甚至可以通過仔細鑄造針對不同的重載使用不同的lambda表達式。

你可以寫一個不這樣做,但它基本上需要重新實現std::function與一個fancier內部狀態。

上述更高級的版本將避免使用線性繼承,因爲這將導致O(n^2)代碼和O(n)遞歸模板深度上的簽名數量。平衡二叉樹繼承將下降到生成的O(n lg n)代碼和O(lg n)深度。

工業強度版本將存儲一次傳入的lambda,使用小對象優化,手動僞vtable使用二進制繼承策略調度函數調用,並將調度函數指針存儲在所述僞代碼中,虛函數表。它將在每個類(而不是每個實例)基礎上使用O(#簽名)* sizeof(函數指針)空間,並且使用的每個實例的開銷與std::function一樣多。

但是對於SO帖子來說這有點多,不是嗎?

start of it

+3

你剛剛在10分鐘內寫下這個答案還是從C++代碼片段中複製它? –

+1

@BryanChen寫道。第一次編譯,這是令人驚訝的部分。如果我使用預先寫好的代碼片段,我希望避免這種低效的線性繼承!我之前做過這樣的事情,使用不使用類型擦除的poly-lambdas,但我不認爲我已經完成了poly -'std :: function'。 – Yakk

+0

@Yakk非常有趣的解決方案! –

2

我喜歡@Yakk提出了很好的解決方案。

這就是說,你可以做避免了std::function S和使用代理功能的拉姆達,可變參數模板和std::forward類似的東西,因爲它遵循:

#include<functional> 
#include<utility> 
#include<iostream> 

template<typename... Args> 
void f(Args&&... args) { 
    auto g = [](int a = 3) { std::cout << a << std::endl; }; 
    g(std::forward<Args>(args)...); 
}; 

int main() { 
    f(2); 
    f(); 
} 

將上述思路在函子,你會有一個類似功能的對象,幾乎相同。

它存在參數未經嚴格驗證的問題。
無論如何,如果使用不當,它會在編譯時失敗。