Cast lambda to std::function with parameter pack

后端 未结 4 918
北海茫月
北海茫月 2021-01-11 17:12

There are several questions on SO that relate to casting lambdas to std::functions, but I have yet to see one that uses a parameter pack for the argument list.

相关标签:
4条回答
  • Here is a solution that will let you call functor without specifying it's template argument:

    #include <functional>
    #include <type_traits>
    
    template <class T> struct Fun_trait {};
    
    template <class T, class... Args, class Ret>
    struct Fun_trait<auto (T::*) (Args...) const -> Ret>
    {
        using F = std::function<auto (Args...) -> Ret>;
    };
    
    
    template <class TReturn, class... TArgs>
    void functor(std::function<TReturn (TArgs...)> f) {}
    
    template <class F>
    std::void_t<decltype(&F::operator())>
    functor(F f)
    {
        return functor<typename Fun_trait<decltype(&F::operator())>::F>(f);
    };
    
    
    int main(int argc, char * argv[])
    {
        auto x = [] (int a, int b) { return a * b; };
    
        // nice and easy:
        functor(x); 
    
        return 0;
    }
    

    This is just a lazy first draft to get you started. You need to expand it to support forwarding and non-const operator().


    It works in 2 stages:

    1st we have Fun_trait who - for pointer method types (e.g. the operator() of a lambda) - has defined an alias F for the required std::function argument type.

    Next we have a overload of your function functor which via SFINAE with std::void_t kicks in only for functors with a non-overloaded operator() (e.g. a lambda) and then using the above trait calls the main function functor with the correct template argument deduced.

    0 讨论(0)
  • 2021-01-11 17:55

    The issue is that the compiler doesn't know that you've intended int, int to be the whole of TArgs, and so tries to deduce the remainder of TArgs from the argument f.

    For example, this would be valid:

    Functor<int, int, int>(std::function<int(int, int, char, float)>{});
    // TArgs := {int, int, [...]                       char, float}
    

    So you need to instruct the compiler to not try to deduce the remainder of TArgs. For example, you could write:

    (*Functor<int, int, int>)(x);
    

    Or you could write Functor with a non-decomposed signature Sig:

    template <Sig>
    void Functor(std::function<Sig> f) {}
    

    Or you could wrap the use of TArgs in the parameter f in a non-deduced context:

    template <typename TReturn, typename ... TArgs>
    void Functor(std::function<std::conditional_t<false, void, TReturn (TArgs...)>> f) {}
    
    0 讨论(0)
  • 2021-01-11 17:55

    It is very rarely a good idea to cast a lambda to a std::function in a template if you are just going to call it. std::function is type-erasure, and templated type erasure only makes sense if you are going to "pass it on" somewhere else and/or return it.

    In any case, try this:

    template <class Sig>
    void Functor(std::function<Sig> f) {}
    
    int main(int argc, char * argv[]) {
      auto x = [] (int a, int b) { return a * b; };
      Functor<int(int, int)>(x);
      return 0;
    }
    

    but you should really just do

    template <class F>
    void Functor(F f) {}
    

    which is perfectly type-safe.

    If you want early type checking, you could write

    template<class Sig, class F>
    struct signature_compatible;
    template<class R, class...Args, class F>
    struct signature_compatible<R(Args...), F> :
      std::is_consructible< R, std::result_of_t<F(Args...)>>
    {};
    

    then do

    template <class Sig, class F>
    void Functor(F f) {
      static_assert( signature_compatible<Sig, F&>::value, "bad signature" );
    }
    

    but only if you really need to.

    0 讨论(0)
  • 2021-01-11 18:00

    This fails:

    #include <functional>
    
    template <typename TReturn, typename ... TArgs>
    void Functor(std::function<TReturn (TArgs...)> f) {}
    
    int main(int argc, char * argv[]) {
        auto x = [] (int a, int b) { return a * b; };
        Functor<int, int, int>(x);
        return 0;
    }
    

    because you're not specifying that the entirety of TArgs... is {int, int}. What you are doing is specifying that the first two types are {int, int}. Effectively, by providing those three types, we've turned the deduction problem into:

    template <typename ... TArgs>
    void Functor(std::function<int(int, int, TArgs...)> f) {}
    
    int main(int argc, char * argv[]) {
        auto x = [] (int a, int b) { return a * b; };
        Functor(x);
        return 0;
    }
    

    This doesn't compile because a lambda isn't a std::function (or derived from one), which is the same reason you couldn't have called this without having provided any types to begin with.

    The non-variadic version doesn't have this problem, since you've provided all the types.


    But really, what you want is:

    template <typename F>
    void Functor(F ) {}
    

    This doesn't lose you any type safety. It's using std::function that loses type information, since that class template exists to type erase.

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