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
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?
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
.
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
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.
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.
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