Recently, I encountered a weird problem with passing a function passed as a parameter to a lambda expression. The code compiled just fine with clang 3.5+, but failed with g++ 5.3 and I wonder whether the problem is in the c++ standard, a non-standard extension in clang, or an invalid syntactic interpretation in GCC.
The example code is fairy simple:
template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
std::future<T>
async(Fn &&fn, Args &&... args)
{
std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>();
auto lambda = [promise, fn, args...](void)
{ promise->set_value(fn(std::move(args)...)); };
send_message(std::make_shared<post_call>(lambda));
return promise->get_future();
};
GCC reported the following:
error: variable ‘fn’ has function type
{ promise->set_value(fn(std::move(args)...)); };
(...)
error: field ‘async(Fn&&, Args&& ...) [with Fn = int (&)(int, int); Args = {int&, int&}; T = int]::<lambda()>::<fn capture>’ invalidly declared function type
auto lambda = [promise, fn, args...](void)
Fortunately, I found a simple workaround, adding a std::function object, encapsulating the function parameter:
template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
std::future<T>
async(Fn &&fn, Args &&... args)
{
std::shared_ptr<std::promise<T>> promise = std::make_shared<std::promise<T>>();
std::function<T(typename std::remove_reference<Args>::type...)> floc = fn;
auto lambda = [promise, floc, args...](void)
{ promise->set_value(floc(std::move(args)...)); };
send_message(std::make_shared<post_call>(lambda));
return promise->get_future();
};
Although I don't fully understand what was wrong in the first piece of code, that successfully compiled with clang and run without errors.
EDIT
I've just noticed, that my solution fails catastrophically, if one of the arguments was supposed to be a reference. So if you have any other suggestions that might work with C++11 (i.e. without specialized lambda captures [c++14 feature]), it would be really cool...
The variable fn
has function type. In particular, it has type Fn = int (&)(int, int)
.
A value of type Fn
(not a reference) is of type int(int,int)
.
This value cannot be stored.
I am vaguely surprised it won't auto-decay it for you. In C++14, you can do:
auto lambda = [promise, fn=fn, args...](void)
{ promise->set_value(fn(std::move(args)...)); };
which should decay the type of fn
(externally) to fn
internally. If that doesn't work:
auto lambda = [promise, fn=std::decay_t<Fn>(fn), args...](void)
{ promise->set_value(fn(std::move(args)...)); };
which explicitly decays it. (Decay is an operation that makes a type suitable for storage).
Second, you should add mutable
:
auto lambda = [promise, fn=std::decay_t<Fn>(fn), args...](void)mutable
{ promise->set_value(fn(std::move(args)...)); };
or the std::move
won't do much. (moving a const value doesn't do much).
Third, you can move the promise in instead of creating a needless shared ptr:
template<typename Fn, typename... Args, typename T = typename std::result_of<Fn(Args...)>::type>
std::future<T>
async(Fn &&fn, Args &&... args)
{
std::promise<T> promise;
std::future<T> ret = promise.get_future();
auto lambda =
[promise=std::move(promise), fn=std::decay_t<Fn>(fn), args...]
() mutable {
promise.set_value(fn(std::move(args)...));
};
send_message(std::make_shared<post_call>(lambda));
return ret;
};
this presumes your post_call
class can handle move-only lambdas (if it is a std::function
it cannot).
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