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



有没有办法从存储在 std::函数中的默认参数的 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++:

答.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)

这些都可以很好地链接在一起。

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

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

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

[现场示例]

但是,没有办法获得相当于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)...
{}
};

以上是 Cam 存储多个operator()的粗糙物体的 C++17 草图。

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

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

这是一个"函数视图",它接受任意数量的签名。 它不拥有要调用的对象。

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)... );
}
};

活生生的例子。

我使用 C++17using ...,因为 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继续存在时才有效。(如果我们对调用视图进行调用视图,则"内部"调用视图也由指针存储,因此必须继续存在;如果我们将调用视图存储在 std 函数中,则情况并非如此)。

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

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

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

最新更新