可以使用默认参数复制包含lambda的std :: function吗?

时间:2017-05-25 06:41:35

标签: c++ lambda function-pointers std-function default-parameters

有没有办法从lambda中恢复类型信息,默认参数存储在std :: function中,该类型的类型中没有这些参数?

std::function<void()> f1 = [](int i = 0){};
std::function<void(int)> f2 = [](int i = 0){};
std::function<void(int)> f3 = f1;  // error
std::function<void()> f4 = f2;     // error

查看std :: function的复制构造函数,其他函数类型没有部分模板特化,所以我想这个信息会丢失,只是你不能将一个类型的函数赋值给另一种类型的函数,即使在内部它们都可以调用该函数。它是否正确?是否有任何解决方法可以实现这一目标?我正在看std :: function :: target,但没有运气,我不是函数类型和指针的专家。

另外,f1(或lambda)如何绑定默认参数?

2 个答案:

答案 0 :(得分:4)

不,这是不可能的,因为默认参数是一组函数声明的属性,而不是函数本身的属性。换句话说,这是完全合法的C ++:

<强> A.cpp

int f(int i = 42);

const int j = f(); // will call f(42)

<强> B.cpp

int f(int i = 314);

const int k = f(); // will call f(314)

<强> F.cpp

int f(int i = 0)
{
  return i;
}

const int x = f(); // will call f(0)

这些都可以很好地联系在一起。

这意味着不可能以某种方式从函数中“检索”默认参数。

您可以使用f4 = f2执行等效的std::bind并提供您自己的默认参数,如下所示:

std::function<void()> f4 = std::bind(f2, 42);

[Live example]

但是,无法获得与f3 = f1等效的内容。

答案 1 :(得分:1)

template<class...Sigs>
strucct functions:std::function<Sigs>...{
  using std::function<Sigs>::operator()...;
  template<class T,
    std::enable_if<!std::is_same<std::decay_t<T>,fundtions>{}>,int> =0
  >
  functions(T&&t):
    std::function<Sigs>(t)...
  {}
};

以上是一个粗略对象的C ++ 17草图,它可以存储多个operator()

更有效的一个只存储对象一次,但存储如何以多种方式调用它。我跳过很多细节。

它不是std::function,而是兼容类型; std函数只存储一种调用对象的方法。

这是一个&#34;功能视图&#34;这需要任意数量的签名。它不拥有所谓的对象。

template<class Sig>
struct pinvoke_t;
template<class R, class...Args>
struct pinvoke_t<R(Args...)> {
    R(*pf)(void*, Args&&...) = 0;
    R invoke(void* p, Args...args)const{
        return pf(p, std::forward<Args>(args)...);
    }
    template<class F, std::enable_if_t<!std::is_same<pinvoke_t, std::decay_t<F>>{}, int> =0>
    pinvoke_t(F& f):
        pf(+[](void* pf, Args&&...args)->R{
            return (*static_cast<F*>(pf))(std::forward<Args>(args)...);
        })
    {}
    pinvoke_t(pinvoke_t const&)=default;
    pinvoke_t& operator=(pinvoke_t const&)=default;
    pinvoke_t()=default;
};

template<class...Sigs>
struct invoke_view:pinvoke_t<Sigs>...
{
    void* pv = 0;
    explicit operator bool()const{ return pv; }
    using pinvoke_t<Sigs>::invoke...;
    template<class F, std::enable_if_t<!std::is_same<invoke_view, std::decay_t<F>>{}, int> =0>
    invoke_view(F&& f):
        pinvoke_t<Sigs>(f)...
    {}
    invoke_view()=default;
    invoke_view(invoke_view const&)=default;
    invoke_view& operator=(invoke_view const&)=default;
    template<class...Args>
    decltype(auto) operator()(Args&&...args)const{
        return invoke( pv, std::forward<Args>(args)... );
    }
};

Live example

我使用C ++ 17 using ...,因为C ++ 14中的二叉树实现很难看。

对于您的用例,它会像:

auto func_object = [](int i = 0){};
invoke_view<void(), void(int)> f1 = func_object;
std::function<void(int)> f3 = f1;  // works
std::function<void()> f4 = f1;     // works

请注意invoke_view中缺少生命周期管理意味着上述内容仅在func_object继续存在时才有效。 (如果我们对一个调用视图进行调用视图,那么&#34; inner&#34;调用视图也会被指针存储,所以必须继续存在;如果我们将调用视图存储在std函数中则不是这样的情况)

目标的终身管理,做得对,需要一些工作。您希望使用带有可选智能指针的小型缓冲区优化,或者使用小型lambda来获得合理的性能,并避免堆分配的开销。

一个简单的天真总是堆分配解决方案会用void*替换unique_ptr<void, void(*)(void*)>并在其中存储{ new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} }(或类似)。

该解决方案使函数对象仅移动;使其可复制还需要键入擦除克隆操作。