Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 Passing function as lambda parameter

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

like image 564
Marandil Avatar asked Jan 15 '16 16:01

Marandil


1 Answers

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

like image 70
Yakk - Adam Nevraumont Avatar answered Oct 19 '22 06:10

Yakk - Adam Nevraumont