可能用默认参数复制包含lambda的std :: function:

问题描述:

有没有什么办法从lambda中恢复类型信息,其默认参数存储在一个std :: function函数中,该函数的类型中没有这些参数?可能用默认参数复制包含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 ::功能的拷贝构造函数,还有其它功能类型的无模板偏特化,所以我想像这个信息将丢失,它只是你不能指定功能的情况下,的一种类型转换为另一种类型的功能,即使它们在内部它们都可以调用该功能。它是否正确?是否有任何解决方法来实现这一目标?我在寻找std :: function :: target,但没有任何运气,我不是专家的函数类型和指针。

在附注中,f1(或lambda)如何绑定默认参数?

+0

你打算如何调用f1或f2(使用默认值)? – nakiya

+1

@nakiya对于信号和插槽的实现,在连接时需要复制插槽(函数),信号使用从信号的调用位置传递的参数来调用插槽。如果允许具有默认参数的插槽连接到具有可以调用它的签名的任何信号,那将会很不错。 –

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

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

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

可以使用std::bind,并提供自己的默认参数,像这样的f4 = f2相当于做:

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

[Live example]

但是,没有办法得到的东西相当于f3 = f1

+0

“你可以使用'std :: bind'”来做相当于'f4 = f2'的操作。你能提供一个这样的例子吗? – nakiya

+1

@nakiya像这样:'std :: function f4 = std :: bind(f2,42)' –

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

上面是一个粗对象凸轮存储多于一个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)...); 
    } 
}; 

Live example

我使用C++ 17 using ...,因为C++ 14中的二叉树实现是丑陋的。

为您的使用情况下,它会像looke:

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继续存在以上才有效。 (如果我们为invoke视图创建一个invoke视图,那么“inner”invoke视图也由指针存储,所以必须继续存在;如果我们将invoke视图存储在一个std函数中,情况不会如此)。

目标的终身管理,做对了,需要一些工作。你会想要使用一个小的缓冲区优化和一个可选的智能指针或某些东西来获得小lambda表达式的合理性能,并避免堆分配的开销。

一个简单朴素的始终堆分配解决方案将取代void*unique_ptr<void, void(*)(void*)>和存储{ new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} }它(或类似)。

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