Search code examples
c++templateslambdavariadic-templatestype-deduction

How to extract lambda's Return Type and Variadic Parameters Pack back from general template<typename T>


I want to create a templated class or function, that receives a lambda, and puts it internally in std::function<> Lambda could have any number of input parameters [](int a, float b, ...) std::function<> should correspond to the lambda's operator()'s type

template <typename T> 
void getLambda(T t) {
   // typedef lambda_traits::ret_type RetType; ??
   // typedef lambda_traits::param_tuple --> somehow back to parameter pack Args...
   std::function<RetType(Args...)> fun(t);
}

int main() {
    int x = 0;
    getLambda([&x](int a, float b, Person c){}); 
}

So I need to somehow extract the Return Type and Parameter Pack

Answer here suggests to use partial spec on lambda's :: operator()

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
    enum { arity = sizeof...(Args) };
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    };
};

But I need a way to convert tuple<> back to parameters pack, to create a proper std::function<> instantiation


Solution

  • template <typename T>
    struct function_traits : public function_traits<decltype(&T::operator())>
    {};
    
    template <typename ClassType, typename ReturnType, typename... Args>
    struct function_traits<ReturnType(ClassType::*)(Args...) const>
    // we specialize for pointers to member function
    {
        using result_type = ReturnType;
        using arg_tuple = std::tuple<Args...>;
        static constexpr auto arity = sizeof...(Args);
    };
    
    template <class F, std::size_t ... Is, class T>
    auto lambda_to_func_impl(F f, std::index_sequence<Is...>, T) {
        return std::function<typename T::result_type(std::tuple_element_t<Is, typename T::arg_tuple>...)>(f);
    }
    
    template <class F>
    auto lambda_to_func(F f) {
        using traits = function_traits<F>;
        return lambda_to_func_impl(f, std::make_index_sequence<traits::arity>{}, traits{});
    }
    

    The code above should do what you want. The main idea, as you can see, is to create an integer pack. This is the non-type template equivalent of variadics. I don't know of any technique by which you can use such a pack without calling another function, so typically in these situations with tuples you'll see a nested "impl" function that does all the work. Once you have the integer pack, you expand it while accessing the tuple (works for getting the values too).

    On a stylistic note: use using, not typename, especially in template heavy code as the former can alias templates too. And don't use that enum trick to store a static value without it using space; compilers will optimize this out anyhow and just using a static constexpr integer is much clearer.