Instantiating C++ lambda by its type

前端 未结 4 1357
野性不改
野性不改 2021-01-01 13:08

I want a way to make functor from function. Now I trying to wrap function call by lambda function and instantiate it later. But compiler says than lambda constructor is dele

相关标签:
4条回答
  • 2021-01-01 13:19

    We can assume that the lambda will always be empty, thus, we can just do a cast from another empty type, since they both have the same memory layout. So we build a wrapper class that makes a default constructible function object:

    template<class F>
    struct wrapper
    {
        static_assert(std::is_empty<F>(), "Lambdas must be empty");
        template<class... Ts>
        auto operator()(Ts&&... xs) const -> decltype(reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...))
        {
            return reinterpret_cast<const F&>(*this)(std::forward<Ts>(xs)...);
        }
    };
    

    Now a static assert is added to make sure that the lambda is always empty. This should always be the case(since its required to decay to a function pointer), but the standard doesn’t explicitly guarantee it. So we use the assert to at least catch the insane implementation of lambdas. Then we just cast the wrapper class to the lambda since both of them are empty.

    Finally the lambda could be constructed like this:

    template <class F>
    void function_caller()
    {
        wrapper<F> f;
        f();
    }
    
    0 讨论(0)
  • 2021-01-01 13:36

    Lambdas do not have default constructors. Ever. The only constructors they sometimes give access to is (depending on what they capture) the copy and/or move constructors.

    If you create a functor with no public default constructor, you'll get the same error.

    In C++17 you can solve this with constexpr lambda and operator+ to decay-to-function pointer. A type that carries a function pointer and invokes it is easy with auto template parameters.

    In C++11 you gotta get a bit hacky.

    template<class F>
    struct stateless_lambda_t {
      static std::aligned_storage_t< sizeof(F), alignof(F) >& data() {
        static std::aligned_storage_t< sizeof(F), alignof(F) > retval;
        return retval;
      };
      template<class Fin,
        std::enable_if_t< !std::is_same< std::decay_t<Fin>, stateless_lambda_t >{}, int> =0
      >
      stateless_lambda_t( Fin&& f ) {
        new ((void*)&data()) F( std::forward<Fin>(f) );
      }
      stateless_lambda_t(stateless_lambda_t const&)=default;
      template<class...Args>
      decltype(auto) operator()(Args&&...args)const {
        return (*static_cast<F*>( (void*)&data() ))(std::forward<Args>(args)...);
      }
      stateless_lambda_t() = default;
    };
    template<class F>
    stateless_lambda_t<std::decay_t<F>> make_stateless( F&& fin ) {
      return {std::forward<F>(fin)};
    }
    

    Now we can:

    auto t = make_stateless([]{ func(); });
    

    and your code works.

    A static_assert or SFINAE that the F is actually an empty type might be a good idea. You know, for quality.

    The use of C++14 features can be replaced with manual decltype and spewing typename and ::type keywords. This answer was originally written for a C++14 question that was closed as a duplicate of this one.

    live example.

    0 讨论(0)
  • 2021-01-01 13:38

    No.

    However I believe that lambdas can be copied, so your functor_caller could take an argument to initialize its attribute.

    Still, instead of reinventing the wheel, I would use std::function instead.

    0 讨论(0)
  • 2021-01-01 13:42

    The code doesn't make sense. Imagine you have a capturing lambda like this:

    {
        int n = 0;
        auto t = [&n](int a) -> int { return n += a; };
    }
    

    What could it possibly mean to default-construct an object of type decltype(t)?

    As @Matthieu suggests, you could wrap the lambda into a function object:

    std::function<int(int)> F = t;
    

    Or you could template your call-site directly on the type of the lambda (or any callable entity):

    template <typename F>
    int compute(int a, int b, F f)
    {
        return a * f(b);  // example
    }
    

    Usage: int a = 0; for (int i : { 1, 3, 5 }) { a += compute(10, i, t); }

    If at all possible, the second style is preferable, since the conversion to std::function is a non-trivial, potentially expensive operation, as is the actual function call through the resulting object. However, if you need to store a uniform collection of heterogeneous callable entities, then std::function may well be the easiest and most convenient solution.

    0 讨论(0)
提交回复
热议问题