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?
A lambda function can take any number of arguments, but can only have one expression.
You can use as many arguments as you want in a lambda function, but it can have only one expression.
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.
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.
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
}
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
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With