Quick Snippet: C++ Type Trait Templates For Lambda Details

Something that comes up occasionally for me is that I’d like to write a template function that accepts a lambda, and then peels back the layers of the lambda to get type information about it. For example, I’d like to have a templated type parameter for the return value of that lambda to be able to wrap a future around it, or get the first argument type and check if I can cast my data to it. It can also be handy to get the exact std::function specialization needed to store the lambda. I was having trouble finding exactly the solution I needed, but I eventually managed to decode the precise C++ incantation needed and it’s not too bad.

This particular version has a little bit of behavior specific to me. One template asks for the type of the first argument to the function, and reports void if there are no arguments rather than generating an error. I have downstream code that handles this case and it works well, but it might not be quite the right behavior for you.

Find it on Godbolt.

#include <cstdio>
#include <functional>
#include <typeinfo> //just used for the demo, not needed by the templates

//Tells us the first argument type in an args tuple
template<typename T>
struct first_arg
{
    using type = std::tuple_element_t<0, T>;
};
//In the case of an empty tuple, report the first type as void
template<>
struct first_arg<std::tuple<>>
{
    using type = void;
};
//These two use a member function pointer type to deduce types for a callable (lambdas, mainly)
template<typename T>
struct memfun_type
{
    using type = void;
};
template<typename Ret, typename Class, typename... Args>
struct memfun_type<Ret (Class::*)(Args...) const>
{
    using fn_type = std::function<Ret(Args...)>;
    using return_type = Ret;
    using arg_types = std::tuple<Args...>;
    using first_arg = typename first_arg<arg_types>::type;
};
//Nice clean template to get the type info of a callable type (lambdas mainly)
template<typename F>
using function_info = memfun_type<decltype(&F::operator())>;

//Here's a usage demo
template<typename F>
void Deduce(F callable)
{
    using FI = function_info<F>;

    printf("The return type of this lambda is: %s\n", typeid(typename FI::return_type).name());
    printf("The first arg type of this lambda is: %s\n", typeid(typename FI::first_arg).name());
    printf("The std::function of this lambda would be: %s\n", typeid(typename FI::fn_type).name());
}

int main()
{
   auto L = [](int x){
        printf("%d\n", x);
        return "fozzy";
    };
    
    Deduce(L);
}

The key realization that makes this work is that the lambda is not the interesting type here. A lambda is secretly a struct containing captured data as member variables and an operator() with the function implementation. It’s that operator() that actually has the desired type info, and so that’s what you want to set up the traits on.

Leave a comment