2016-06-09 79 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(); 
} 

将上述思路在函子,你会有一个类似功能的对象,几乎相同。

它存在参数未经严格验证的问题。
无论如何,如果使用不当,它会在编译时失败。