Search code examples
c++lambdac++23c++-templates

how to convert lambda function to another one taking tuple of parameters


I need to create a wrapper for lambda function that would take a tuple of parameters of the original lambda function and use its implementation. I'm using C++23 compiler. This is the function I came up with:

template<class Fn>
constexpr auto wrap(Fn fn) 
{ 
    return [fn](typename lambda_traits<Fn>::Args&& t) { return std::apply(fn, std::forward<decltype(t)>(t)); };
}

To make it work I need to create a lambda_traits class the instantiation of which on lambda type deduces the Args type, which is a tuple of lambda parameters. For example, for lambda function [](int i, int j){} the Args would be tuple<int, int>.

After experimenting with possible solutions to lambda_traits I finally cheated and delegated the type deduction to std::function.

template<class Fn> struct lambda_traits;

template< class R, class ... A >
struct lambda_traits<R(A...)>
{
    using Ret = R;
    using Args = std::tuple<A...>;

    constexpr lambda_traits(std::function<R(A...)>);
};

template< class R, class ... A >
lambda_traits(std::function<R(A...)>) -> lambda_traits<R(A...)>;

template<class Fn> 
struct lambda_traits : decltype(lambda_traits(std::function(std::declval<Fn>())))
{
};

std::function looks like a kludge here, I tried to replace it but couldn't find the way to make it work. Can you rework this solution removing std::function?

Another problem, the implicit conversions are not happening in some cases, probably something is wrong with types somewhere. Can you fix that?

This works:

static_assert(
    wrap([](std::string s) { return s.size();})(std::tuple{"abc"}) == 3);

This doesn't:

static_assert(
    wrap([](const std::string& s) { return s.size();})(std::tuple{"abc"}) == 3);

Compiler explorer demo is here.


Solution

  • Can you rework this solution removing std::function?

    You can reimplement the deduction guides for std::function, which is not difficult

    template<class Fn> struct lambda_traits ;
    
    template< class R, class G, class ... A >
    struct lambda_traits<R(G::*)(A...) const>
    {
        using Ret = R;
        using Args = std::tuple<A...>;
    };
    
    template<class Fn> 
    struct lambda_traits : lambda_traits<decltype(&Fn::operator())>
    {
    };
    

    Demo

    Another problem, the implicit conversions are not happening in some cases, probably something is wrong with types somewhere. Can you fix that?

    Because you can't construct a tuple<const std::string&> from a tuple<const char*> which would result in a dangling reference, so the library rejects it.