Search code examples
c++c++11lambdashared-ptr

Smart pointer to lambda


I'm trying to make a function that accepts a shared pointer to some functor. With manually crafted functors there're no problems, but with lambda there are. I know that I can't use decltype with lambda - every new lambda declaration creates a new type. Right now I'm writing:

auto lambda = [](int a, float b)->int
{
    return 42;
};
using LambdaType = decltype(lambda);

shared_ptr<LambdaType> ptr{ new LambdaType{ lambda } };

It works, but looks ugly. Moreover there's a copy constructor call! Is there any way to simplify?


Solution

  • Lambdas are merely auto written invokable objects to make simple code simple. It you want something beyond their default automatic storage behavior, write the type they write yourself.

    It is illegal to have a lambda type in an unevaluated context. In an evaluated context, it creates a lambda in automatic storage. You want it on the free store. This requires at least logically a copy.

    A horrible hack involving violating the unevaluated context rule, sizeof/alignof, aligned_storage_t, placement new, possibly unbounded compile time recursion (or maybe one with a static_assert), returning pointers to local variables, and the aliasing constructor of shared ptr, and requiring callers to write insane code might avoid calling the copy/move. But it is a bad idea, and simply using invokable objects is easier.

    Of course, accepting the copy/move makes it trivial. But at that point, just use std::function unless you need something like varargs.

    You state you do not want to force users to use std::function; but std::function would implicitly convert a compatible lambda into itself.

    If you are willing to accept a copy, we can do this:

    template<class T>
    std::shared_ptr<std::decay_t<T>>
    auto_shared( T&& t ) {
      return std::make_shared<std::decay_t<T>>(std::forward<T>(t));
    }
    

    then auto ptr = auto_shared( [x=0]()mutable{ return x++; } ); is a non-type-erased shared pointer to a counting lambda. The lambda is copied (well, moved) into the shared storage.

    If you want to avoid that copy, the client can write a manual function object and call make_shared<X>(ctor_args) on it.

    There is no reasonable way to separate a lambdas type from its construction in C++ at this point.