Is there any way to recover type information from a lambda with default parameters stored in a std::function that does not have those 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's copy constructor, there is no partial template specialization for other function types, so I'd imagine this information is lost and it is just a case that you can't assign a function of one type to a function of another type, even if internally they can both call the function. Is this correct? Are there any work-arounds to achieve this? I'm looking at std::function::target, but haven't had any luck, I'm no expert on function types and pointers.
On a side note, how does f1(or the lambda) bind the default parameter?
You can recover the desired behavior by always using thread-local copies of the std::function because they'll each have an isolated copy of the state variables.
Lambda Symbol (λ)
To create a lambda function first write keyword lambda followed by one of more arguments separated by comma ( , ), followed by colon a ( : ), followed by a single line expression. Here we are using two arguments x and y , expression after colon is the body of the lambda function.
Permalink. All the alternatives to passing a lambda by value actually capture a lambda's address, be it by const l-value reference, by non-const l-value reference, by universal reference, or by pointer.
No, that is not possible, because default arguments are a property of a set of a function's declarations, not of the function itself. In other words, this is perfectly 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)
These can all be linked together just fine.
Which means it's not possible to somehow "retrieve" a default argument from a function.
You can do the equivalent of f4 = f2
using std::bind
and providing your own default argument, like this:
std::function<void()> f4 = std::bind(f2, 42);
[Live example]
However, there is no way to get something equivalent to 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)...
{}
};
the above is a C++17 sketch of a crude object that cam store more than one operator()
.
A more efficient one would only store the object once, but store how to call it many ways. And I skipped many details.
It isn't really a std::function
, but a compatible type; std function only stores one way to call the object.
Here is a "function view" that takes any number of signatures. It does not own the to-be-called object.
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.
I use C++17 using ...
because the binary tree implementation in C++14 is ugly.
For your use case, it would looke like:
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 lifetime management in invoke_view
means that the above only works when func_object
continues to exist. (If we make an invoke view to an invoke view, the "inner" invoke view is stored by pointer as well, so must continue to exist; not the case if we store the invoke view in a std function).
Lifetime management of the target, done right, takes a bit of work. You'd want to use a small buffer optimization with an optional smart pointer or something to get reasonable performance with small lambdas and avoid the overhead of the heap allocation.
A simple naive always heap allocating solution would replace the void*
with a unique_ptr<void, void(*)(void*)>
and store { new T(t), [](void* ptr){static_cast<T*>(ptr)->~T();} }
in it (or similar).
That solution makes the function object move-only; making it copyable requires also type erasing a clone operation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With