Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Count the number of arguments in a lambda

I need to know the exact number of arguments that a lambda has. I do not care for their types, I just need a count.

auto lambda0 = [&]() { ... };
auto lambda1 = [&](int32_t a) { ... };
auto lambda2 = [&](int32_t a, auto b) { ... };

lambda_details<decltype(lambda0)>::argument_count; // Equals 0
lambda_details<decltype(lambda1)>::argument_count; // Equals 1
lambda_details<decltype(lambda2)>::argument_count; // Equals 2

Detecting variadic lambdas would also be nice so that I can deal with that edge case as well.

auto lambda_variadic = [&](auto... args){ ... };

lambda_details<decltype(lambda_variadic)>::is_variadic; // Equals true

How can I get this information?

like image 802
Rick de Water Avatar asked Jan 27 '19 15:01

Rick de Water


People also ask

How many arguments are there in lambda function?

A lambda function can take any number of arguments, but can only have one expression.

How many arguments are there in lambda function Python?

You can use as many arguments as you want in a lambda function, but it can have only one expression.

How many expressions can lambda evaluate?

A lambda function is an anonymous function (i.e., defined without a name) that can take any number of arguments but, unlike normal functions, evaluates and returns only one expression.

Can lambda have multiple inputs?

Create a Lambda function arguments : Input arguments of the function. The function allows multiple input arguments. expression : What the function will do in a single expression. Lambda only accepts one expression, but it can generate multiple outputs.


2 Answers

You can create an object that can go into any parameter by overloading conversion operator. From there just test if the lambda is callable with a given number of such arguments, counting down from some arbitrary large number. If the lambda happens to be callable on the first try (with given arbitrary large number of arguments), we can assume it is variadic:

#include <iostream>
#include <utility>
#include <type_traits>


struct any_argument {
    template <typename T>
    operator T&&() const;
};


template <typename Lambda, typename Is, typename = void>
struct can_accept_impl
: std::false_type
{};

template <typename Lambda, std::size_t ...Is>
struct can_accept_impl<Lambda, std::index_sequence<Is...>, 
                       decltype(std::declval<Lambda>()(((void)Is, any_argument{})...), void())>
: std::true_type
{};

template <typename Lambda, std::size_t N>
struct can_accept
: can_accept_impl<Lambda, std::make_index_sequence<N>>
{};


template <typename Lambda, std::size_t Max, std::size_t N, typename = void>
struct lambda_details_impl
: lambda_details_impl<Lambda, Max, N - 1>
{};

template <typename Lambda, std::size_t Max, std::size_t N>
struct lambda_details_impl<Lambda, Max, N, std::enable_if_t<can_accept<Lambda, N>::value>>
{
    static constexpr bool is_variadic = (N == Max);
    static constexpr std::size_t argument_count = N;
};

template <typename Lambda, std::size_t Max = 50>
struct lambda_details
: lambda_details_impl<Lambda, Max, Max>
{};


int main()
{
    auto lambda0 = []() {};
    auto lambda1 = [](int a) {};
    auto lambda2 = [](int a, auto b) {};
    auto lambda3 = [](int a, auto b, char = 'a') {};
    auto lambda4 = [](int a, auto b, char = 'a', auto...) {};

    std::cout << lambda_details<decltype(lambda0)>::is_variadic << " " << lambda_details<decltype(lambda0)>::argument_count << "\n"; // 0 0
    std::cout << lambda_details<decltype(lambda1)>::is_variadic << " " << lambda_details<decltype(lambda1)>::argument_count << "\n"; // 0 1
    std::cout << lambda_details<decltype(lambda2)>::is_variadic << " " << lambda_details<decltype(lambda2)>::argument_count << "\n"; // 0 2
    std::cout << lambda_details<decltype(lambda3)>::is_variadic << " " << lambda_details<decltype(lambda3)>::argument_count << "\n"; // 0 3
    std::cout << lambda_details<decltype(lambda4)>::is_variadic << " " << lambda_details<decltype(lambda4)>::argument_count << "\n"; // 1 50
}
like image 95
yuri kilochek Avatar answered Nov 15 '22 16:11

yuri kilochek


I have solved it using a modified version of @yuri kilochek's answer.

Instead of starting from 50 arguments and counting down, we start at zero and count up. When we get a match we know the minimum amount of arguments required to call the lambda. We then keep searching up until a sane maximum to see if there is a maximum amount of arguments (this can happen when you have default arguments).

If the argument count limit is reached, we assume the lambda to be variadic.

This implementation reduces the amount of template instantiations for non variadic lambdas significantly. It also gives us the minimum amount of arguments for all lambdas, and the maximum amount of arguments for any non-variadic lambdas.

Again, big thanks to Yuri Kilochek for laying the foundation for this elegant solution. Check his answer for more details about the implementation.

struct any_argument
{
    template <typename T>
    operator T && () const;
};

template <typename Lambda, typename Is, typename = void>
struct can_accept_impl : std::false_type
{};

template <typename Lambda, std::size_t ...Is>
struct can_accept_impl <Lambda, std::index_sequence<Is...>, decltype(std::declval<Lambda>()(((void)Is, any_argument{})...), void())> : std::true_type
{};

template <typename Lambda, std::size_t N>
struct can_accept : can_accept_impl<Lambda, std::make_index_sequence<N>>
{};

template <typename Lambda, std::size_t N, size_t Max, typename = void>
struct lambda_details_maximum
{
    static constexpr size_t maximum_argument_count = N - 1;
    static constexpr bool is_variadic = false;
};

template <typename Lambda, std::size_t N, size_t Max>
struct lambda_details_maximum<Lambda, N, Max, std::enable_if_t<can_accept<Lambda, N>::value && (N <= Max)>> : lambda_details_maximum<Lambda, N + 1, Max>
{};

template <typename Lambda, std::size_t N, size_t Max>
struct lambda_details_maximum<Lambda, N, Max, std::enable_if_t<can_accept<Lambda, N>::value && (N > Max)>>
{
    static constexpr bool is_variadic = true;
};

template <typename Lambda, std::size_t N, size_t Max, typename = void>
struct lambda_details_minimum : lambda_details_minimum<Lambda, N + 1, Max>
{
    static_assert(N <= Max, "Argument limit reached");
};

template <typename Lambda, std::size_t N, size_t Max>
struct lambda_details_minimum<Lambda, N, Max, std::enable_if_t<can_accept<Lambda, N>::value>> : lambda_details_maximum<Lambda, N, Max>
{
    static constexpr size_t minimum_argument_count = N;
};

template <typename Lambda, size_t Max = 50>
struct lambda_details : lambda_details_minimum<Lambda, 0, Max>
{};

Another important thing to note is that any_argument doesn't automatically play nice with operators. You will have to overload every single one if you want it to work with auto arguments that are operated upon (e.g. [](auto a) { return a * 2; }). It will end up looking more like this:

struct any_argument
{
    template <typename T> operator T && () const;

    any_argument& operator ++();
    any_argument& operator ++(int);
    any_argument& operator --();
    any_argument& operator --(int);

    template <typename T> friend any_argument operator + (const any_argument&, const T&);
    template <typename T> friend any_argument operator + (const T&, const any_argument&);
    template <typename T> friend any_argument operator - (const any_argument&, const T&);
    template <typename T> friend any_argument operator - (const T&, const any_argument&);
    template <typename T> friend any_argument operator * (const any_argument&, const T&);
    template <typename T> friend any_argument operator * (const T&, const any_argument&);
    template <typename T> friend any_argument operator / (const any_argument&, const T&);
    template <typename T> friend any_argument operator / (const T&, const any_argument&);

    // And every other operator in existence
};
like image 38
Rick de Water Avatar answered Nov 15 '22 16:11

Rick de Water