Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to copy std::function containing lambda with default parameters?

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?

like image 609
A-n-t-h-o-n-y Avatar asked May 25 '17 06:05

A-n-t-h-o-n-y


People also ask

Can I copy a std :: function?

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.

How do you write lambda in copy?

Lambda Symbol (λ)

How do you convert lambda to a function?

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.

How do you pass lambda function in CPP?

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.


2 Answers

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.

like image 86
Angew is no longer proud of SO Avatar answered Sep 22 '22 11:09

Angew is no longer proud of SO


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.

like image 32
Yakk - Adam Nevraumont Avatar answered Sep 20 '22 11:09

Yakk - Adam Nevraumont