Подсчитайте количество аргументов в лямбде - PullRequest
0 голосов
/ 27 января 2019

Мне нужно знать точное количество аргументов лямбды.Меня не волнуют их типы, мне просто нужен счет.

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

Было бы неплохо обнаруживать вариабельные лямбды, чтобы я мог справиться и с этим крайним случаем.

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

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

Как я могу получить эту информацию?

Ответы [ 3 ]

0 голосов
/ 27 января 2019

Вы можете создать объект, который может входить в любой параметр, перегружая оператор преобразования.Оттуда просто проверьте, может ли лямбда вызываться с заданным количеством таких аргументов, считая от некоторого произвольного большого числа.Если лямбда может быть вызвана с первой попытки (с заданным произвольным большим количеством аргументов), мы можем предположить, что она переменная:

#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
}
0 голосов
/ 28 января 2019

Я решил это, используя модифицированную версию ответа @yuri kilochek.

Вместо того, чтобы начинать с 50 аргументов и вести обратный отсчет, мы начинаем с нуля и считаем до.Когда мы получаем совпадение, мы знаем минимальное количество аргументов, необходимых для вызова лямбды.Затем мы продолжаем поиск до нормального максимума, чтобы увидеть, есть ли максимальное количество аргументов (это может случиться, когда у вас есть аргументы по умолчанию).

Если достигнут предел количества аргументов, мы предполагаем, что лямбда-выражение является вариадическим.

Эта реализация значительно сокращает количество экземпляров шаблона для невариантных лямбд.Это также дает нам минимальное количество аргументов для всех лямбд и максимальное количество аргументов для любых невариантных лямбд.Посмотрите его ответ для более подробной информации о реализации.

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>
{};

Еще одна важная вещь, которую стоит отметить, это то, что any_argument автоматически не работает с операторами.Вам придется перегружать каждый из них, если вы хотите, чтобы он работал с auto аргументами, с которыми оперируют (например, [](auto a) { return a * 2; }).В конечном итоге это будет выглядеть примерно так:

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
};
0 голосов
/ 27 января 2019

Я не знаю, как подсчитать все аргументы обобщённой лямбды [ edit : но Юрий Килочек знает, как это сделать: см. Его ответ для отличного решения].

Для неуниверсальных лямбд, как предложил Игорь Тандетник, вы можете определить типы (возврат и аргументы) указателя на operator() и сосчитать аргументы.

Что-то следующее

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...) const)
 { return sizeof...(Args); }

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...))
 { return sizeof...(Args); }

template <typename L>
constexpr auto countArguments (L)
 { return cah(&L::operator()); }

Но, к сожалению, это не работает, когда вы вводите аргумент auto, потому что с аргументом auto вы преобразуете operator() в шаблонной функции.

Об обнаружении переменнойЛямбда, вы можете обнаружить функцию только с переменным списком аргументов (позвольте мне назвать это «чисто переменным»), как ваш lambda_variadic, пытаясь вызвать ее с нуля и с (на примере) 50 аргументами данного типа.

Я имею в виду что-то следующее

template <typename T, std::size_t>
struct getType
 { using type = T; };

template <typename T, std::size_t N>
using getType_t = typename getType<T, N>::type;

// isPureVariadic arguments helper
template <typename T>
constexpr std::false_type ipvh (...);

// isPureVariadic arguments helper
template <typename T, typename F, std::size_t ... Is>
constexpr auto ipvh (F f, std::index_sequence<Is...>)
   -> decltype( f(std::declval<getType_t<T, Is>>()...), std::true_type{} );

template <typename F>
constexpr bool isPureVariadic (F f)
 { return
      decltype(ipvh<int>(f, std::make_index_sequence<0u>{}))::value
   && decltype(ipvh<int>(f, std::make_index_sequence<50u>{}))::value; }

, но это не идеально, потому что дает ложные срабатывания и ложные отрицания.

Проблема в том, что при проверке с помощью«не чистая вариадическая лямбда» как

 auto lambda_variadic2 = [&](std::string, auto... args){ ... };

, который является переменным, но первый аргумент не принимает int, не определяется как «чистый переменный»;к сожалению, следующая лямбда

 auto lambda_variadic3 = [&](long, auto... args){ ... };

определяется как "чисто переменная", потому что первый аргумент принимает int.

Чтобы избежать этой проблемы, вы можете изменить функцию для проверки вызовас 50 аргументами двух несовместимых типов;в качестве примера

template <typename F>
constexpr bool isPureVariadic (F f)
 { return
      decltype(ipvh<int>(f, std::make_index_sequence<0u>{}))::value
   && decltype(ipvh<int>(f, std::make_index_sequence<50u>{}))::value
   && decltype(ipvh<std::string>(f, std::make_index_sequence<50u>{}))::value; }

Другая проблема заключается в том, что обнаруживаются как «чисто виртуальные», а также не вариационные функции generic-lambda, получающие число аргументов, превышающее проверяемое число (в данном примере - 50).

И остается проблема, заключающаяся в том, что это решение не определяет lambda_variadic2 (не чистая вариадическая лямбда) как вариадическое.

Ниже приводится полный пример компиляции с лучшим, что я могу представить о вашемвопрос

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

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...) const)
 { return sizeof...(Args); }

// count arguments helper
template <typename R, typename T, typename ... Args>
constexpr std::size_t  cah (R(T::*)(Args...))
 { return sizeof...(Args); }

template <typename L>
constexpr auto countArguments (L)
 { return cah(&L::operator()); }

template <typename T, std::size_t>
struct getType
 { using type = T; };

template <typename T, std::size_t N>
using getType_t = typename getType<T, N>::type;

// isPureVariadic arguments helper
template <typename T>
constexpr std::false_type ipvh (...);

// isPureVariadic arguments helper
template <typename T, typename F, std::size_t ... Is>
constexpr auto ipvh (F f, std::index_sequence<Is...>)
   -> decltype( f(std::declval<getType_t<T, Is>>()...), std::true_type{} );

template <typename F>
constexpr bool isPureVariadic (F f)
 { return
      decltype(ipvh<int>(f, std::make_index_sequence<0u>{}))::value
   && decltype(ipvh<int>(f, std::make_index_sequence<50u>{}))::value; }


int main() {
   auto lambda0 = [&]() {};
   auto lambda1 = [&](int) {};
   auto lambda2 = [&](int, auto) {};
   auto lambda3 = [&](auto...) {};

   std::cout << countArguments(lambda0) << std::endl;
   std::cout << countArguments(lambda1) << std::endl;
   // std::cout << countArguments(lambda2) << std::endl; // compilation error
   // std::cout << countArguments(lambda3) << std::endl; // compilation error

   std::cout << isPureVariadic(lambda0) << std::endl;
   std::cout << isPureVariadic(lambda1) << std::endl;
   std::cout << isPureVariadic(lambda2) << std::endl;
   std::cout << isPureVariadic(lambda3) << std::endl;
}
...