Gabriel
Gabriel

Reputation: 9432

Get argument type of lambda with auto specifier

I have a meta function which gives me the type of the I-th argument of a lambda/function:

#include <iostream>
#include <tuple>

namespace details
{
    //! Spezialization for Funktion-Pointers
    template<typename Ret, typename... Args>
    std::tuple<Args...> getArgs(Ret (*)(Args...));

    //! Spezialization for Functor/Lambdas
    template<typename F, typename Ret, typename... Args>
    std::tuple<Args...> getArgs(Ret (F::*)(Args...));

    //! Spezialization for Functor/Lambdas
    template<typename F, typename Ret, typename... Args>
    std::tuple<Args...> getArgs(Ret (F::*)(Args...) const);

}; // namespace details

template<typename F, std::size_t I>
using GetArg = std::tuple_element_t<I, decltype(details::getArgs(std::declval<F>()))>;

int main()
{
    auto f1 = [](int a, int b){};
    static_assert(std::is_same<GetArg<decltype(f1), 0>, int>{}, "Not the same!");

    // auto f2 = [](int a, auto b){};
    // static_assert(std::is_same<GetArg<decltype(f2), 0>, int>{}, "Not the same!");
}

Live

The second lambda with auto specifier does not compile as my spezializations are not matched, because auto is like a template parameter T, which is not known. Is there a way of making this work for f2 as well?

Since the lambda is an opaque type and a template function has no type unless instantiated with template argument types, I have not really any idea of how to make this work? Is it impossible?

Upvotes: 3

Views: 890

Answers (2)

Gelldur
Gelldur

Reputation: 11558

I think you miss this:

template <typename F>
decltype(getArgs(&F::operator())) getArgs(F);

So final version would be:

namespace details
{
//! Spezialization for Funktion-Pointers
template <typename Ret, typename... Args>
std::tuple<Args...> getArgs(Ret (*)(Args...));

//! Spezialization for Functor/Lambdas
template <typename F, typename Ret, typename... Args>
std::tuple<Args...> getArgs(Ret (F::*)(Args...));

//! Spezialization for Functor/Lambdas
template <typename F, typename Ret, typename... Args>
std::tuple<Args...> getArgs(Ret (F::*)(Args...) const);

template <typename F>
decltype(getArgs(&F::operator())) getArgs(F);

}; // namespace details

template <typename F, std::size_t I>
using GetArg = std::tuple_element_t<I, decltype(details::getArgs(std::declval<F>()))>;

Now it compiles: https://godbolt.org/z/r1PsE36Wx

Upvotes: 1

max66
max66

Reputation: 66200

Is there a way of making this work for f2 as well?

No, as far I know.

You can see a generic lambda (a lambda with one or more auto arguments) almost as a template function (wrapped in a class).

Excluding the class wrapping, you can see

[](int a, auto b){};

almost as

template <typename T>
void foo (int a, T b)
 { };

You can't deduce the type of b from the lambda exactly as you can't deduce the type of b from foo(): it's decided calling the function (deducing the type form the argument) or explicating it (something as foo<int>).

But if you simply write decltype(foo), the compiler can't decide which type is T so gives an error.

Anyway, I get an error also compiling

static_assert(std::is_same<GetArg<decltype(f1), 0>, int>{}, "Not the same!");

I suppose you have to write GetArg as follows

using GetArg = std::tuple_element_t<I, decltype(details::getArgs(&F::operator()))>;
// ..............................................................^^^^^^^^^^^^^^

Or you can maintain your actual GetArg but calling it with +f1

static_assert(std::is_same<GetArg<decltype(+f1), 0>, int>{}, "Not the same!");
// ........................................^^^

that is: converting the lambda to a function pointer.

Upvotes: 1

Related Questions