有没有办法从存储在 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();} }
存储在其中(或类似)。
该解决方案使函数对象仅移动;使其可复制还需要类型擦除克隆操作。