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

Possible to copy std::function containing lambda with default parameters?

有什么方法可以从默认参数存储在 std::function 而其类型中没有这些参数的 lambda 中恢复类型信息?

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)如何绑定默认参数?

不,那是不可能的,因为默认参数是一组函数声明的 属性,而不是函数本身。换句话说,这是完全合法的 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)

这些都可以链接在一起就好了。

这意味着不可能 "retrieve" 函数的默认参数。

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

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

[Live example]

然而,没有办法得到等同于f3 = f1的东西。

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 函数只存储一种调用对象的方法。

这里有一个 "function view" 可以接受任意数量的签名。它不拥有被调用的对象。

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 继续存在时有效。 (如果我们将调用视图创建为调用视图,"inner" 调用视图也由指针存储,因此必须继续存在;如果我们将调用视图存储在标准函数中,则情况并非如此)。

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

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

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