Can I copy std :: function containing a lambda with default parameters?

Is there a way to recover type information from a lambda with default parameters stored in std :: function, which does not have these parameters in its type?

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 

Looking at std :: function copy constructor, for other types of functions there is no partial specialized specialization, so I would suggest that this information is lost, and this is just the case when you cannot assign a function of one type to a function of another type, even if inside they can both call a function. It's right? Are there any problems to achieve this? I look at std :: function :: target, but I'm out of luck, I'm not an expert on function types and pointers.

On the other hand, how does f1 (or lambda) bind the default parameter?

+7
c ++ lambda default-parameters function-pointers std-function
source share
2 answers

No, this is not possible, because the default arguments are a property of a set of function declarations, not the function itself. In other words, this is completely legal 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) 

All of them can be connected with each other very well.

This means that it is not possible to β€œextract” the default argument from the function in any way.

You can make the equivalent of f4 = f2 with std::bind and provide your own default argument, for example:

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

[Live example]

However, there is no way to get something equivalent to f3 = f1 .

+4
source share
 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)... {} }; 

the above sketch is a C ++ 17 gross object in which a cam stores more than one operator() .

A more efficient one will store an object only once, but save what you call it in many ways. And I missed a lot of details.

This is not really a std::function , but a compatible type; The std function saves only one way to call an object.

Here is a "function representation" that accepts any number of signatures. He does not own the object to be called.

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

Living example .

I use C ++ 17 using ... because the binary tree implementation in C ++ 14 is ugly.

For your use case, it will look like this:

 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 

note that the lack of lifecycle management in invoke_view means that the above only works when func_object continues to exist. (If we call the view to represent the call, the "internal" call view is also stored by the pointer, so it should go on, and not the case if we store the call view in the std function).

Managing a goal’s life cycle done right takes a bit of work. You want to use a little buffer optimization with an optional smart pointer or something to get reasonable performance with small lambdas and avoid the heap allocation overhead.

A simple, naive heap allocation solution will replace void* with unique_ptr<void, void(*)(void*)> and save { new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} } in it (or similar).

This decision makes the function object only to move; copying it also requires a type that erases the clone operation.

+1
source share

All Articles