Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture std::promise in a lambda C++14

I want to make a state machine which works processes the submitted signals in its own thread. I use Visual Studio 2015, so C++11 and partially C++14 is supported. Signals are stored in containers. Each signal is represented as an std::function. I would like to wait from the client until the state machine processes the signal that was submitted, so it is a kind of synchronous signal.

My problem is: I cannot capture an std::promise into a lambda and add it to the container.

#include <functional>
#include <future>
#include <list>

std::list<std::function<int()>> callbacks;

void addToCallbacks(std::function<int()>&& callback)
{
    callbacks.push_back(std::move(callback));
}

int main()
{
    std::promise<int> prom;
    auto fut = prom.get_future();

    // I have made the lambda mutable, so that the promise is not const, so that I can call the set_value
    auto callback = [proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; };

    // This does not compile
    addToCallbacks(std::move(callback));

    // This does not compile either, however this lambda is a temporal value (lvalue)
    addToCallbacks([proms{ std::move(prom) }]() mutable { proms.set_value(5); return 5; });

    return 0;
}

What are the solutions if

  • I want to avoid capturing the promise by reference
  • I want to avoid capturing a * pointer or a shared_ptr to the promise

It would be nice to embed the promise into the class somehow that the lambda generates. This means that the lambda is not copyable any more, only moveable. Is it possible at all?

like image 261
user2281723 Avatar asked Oct 30 '15 12:10

user2281723


4 Answers

std::function can only be constructed from functors that are copyable. From [func.wrap.func.con]:

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

Requires: F shall be CopyConstructible.

std::promise is non-copyable, so there's no way to stick a functor with this member into a std::function. Period.

Given that you want your functor to actually take ownership of the promise, this doesn't leave you many options. Pretty much std::shared_ptr<std::promise>. Any other option either doesn't work (e.g. std::unique_ptr<std::promise>), leaves you with a dangling object (e.g. std::reference_wrapper<std::promise>), or leaves you with memory-management issues (e.g. std::promise*).


You could, however, use something other than std::function. You can take a look at Yakk's task idea here, as well as dyp's function_mo here, both of which create movable flavors of std::function.

like image 75
Barry Avatar answered Nov 16 '22 07:11

Barry


It's trivial to roll your own polymorphic function class. This example fixes the argument and return types, but a little more work could templatise them if desired.

#include <iostream>
#include <functional>
#include <future>
#include <list>

// declare a non-polymorphic container for any function object that takes zero args and returns an int
// in addition, the contained function need not be copyable
class move_only_function
{
    // define the concept of being callable while mutable
    struct concept
    {
        concept() = default;
        concept(concept&&) = default;
        concept& operator=(concept&&) = default;
        concept(const concept&) = delete;
        concept& operator=(const concept&) = default;
        virtual ~concept() = default;

        virtual int call() = 0;
    };

    // model the concept for any given function object
    template<class F>
    struct model : concept
    {
        model(F&& f)
        : _f(std::move(f))
        {}

        int call() override
        {
            return _f();
        }

        F _f;
    };

public:
    // provide a public interface
    int operator()()  // note: not const
    {
        return _ptr->call();
    }

    // provide a constructor taking any appropriate object
    template<class FI>
    move_only_function(FI&& f)
    : _ptr(std::make_unique<model<FI>>(std::move(f)))
    {}

private:
    std::unique_ptr<concept> _ptr;
};

std::list<move_only_function> callbacks;

void addToCallbacks(move_only_function&& callback)
{
    callbacks.push_back(std::move(callback));
}

int main()
{
    std::promise<int> prom;
    auto fut = prom.get_future();

    // I have made the lambda mutable, so that the promise is not const, so that I can call the set_value
    auto callback = [proms=std::move(prom)]() mutable { proms.set_value(5); return 5; };

    // This now compiles
    addToCallbacks(std::move(callback));

    std::promise<int> prom2;
    auto fut2 = prom2.get_future();

    // this also compiles
    addToCallbacks([proms = std::move(prom2)]() mutable { proms.set_value(6); return 6; });

    for (auto& f : callbacks)
    {
        std::cout << "call returns " << f() << std::endl;
    }

    std::cout << "fut = " << fut.get() << std::endl;
    std::cout << "fut2 = " << fut2.get() << std::endl;

    return 0;
}

expected output:

call returns 5
call returns 6
fut = 5
fut2 = 6
like image 28
Richard Hodges Avatar answered Nov 16 '22 07:11

Richard Hodges


Good news: C++23 will resolve that long-standing issue.

In the (still evolving future) standard, there is a std::move_only_function, which will allow precisely such use cases as detailed here.

like image 2
Ichthyo Avatar answered Nov 16 '22 07:11

Ichthyo


Another simple approach may be to use destructive copy idiom and wrap movable-only type into trivial CopyConstructible struct:

#include <functional>
#include <future>
#include <type_traits>

template <typename T>
struct destructive_copy_constructible
{
    mutable T value;

    destructive_copy_constructible() {}

    destructive_copy_constructible(T&& v): value(std::move(v)) {}

    destructive_copy_constructible(const destructive_copy_constructible<T>& rhs)
        : value(std::move(rhs.value))
    {}

    destructive_copy_constructible(destructive_copy_constructible<T>&& rhs) = default;

    destructive_copy_constructible&
    operator=(const destructive_copy_constructible<T>& rhs) = delete;

    destructive_copy_constructible&
    operator=(destructive_copy_constructible<T>&& rhs) = delete;
};

template <typename T>    
using dcc_t = 
    destructive_copy_constructible<typename std::remove_reference<T>::type>;

template <typename T>
inline dcc_t<T> move_to_dcc(T&& r)
{
    return dcc_t<T>(std::move(r));
}

int main()
{
    std::promise<int> result;

    std::function<void()> f = [r = move_to_dcc(result)]()
    {
        r.value.set_value(42);
    };

    return 0;
}

Despite the fact that destructive copy idiom is considered dangerous and obsoleted by move idiom, it still can be useful, at least to cover such std::function hole.

Advantage here is zero overhead (no copy/dynamic memory allocations) comparing to proposed std::shared_ptr and move_only_function solutions. And danger of misusing copied original object is mostly mitigated by using move-like syntax that clearly describes destructive copy/move semantics.

Another useful side effect is that you don't have to declare entire lambda mutable to set std::promise value because it's already declared so in DCC wrapper.

like image 1
Rost Avatar answered Nov 16 '22 09:11

Rost