统计 lambda 表达式中参数的个数

14

我需要知道lambda函数的确切参数数量,我不关心它们的类型,只需要计数。

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

检测变长Lambda表达式也很好,这样我也可以处理那种特殊情况。

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

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

如何获取这些信息?


2
类似于这样的代码(https://rextester.com/LIA88324)适用于非变参、非泛型 lambda 表达式(或者一般情况下,任何只有一个非模板化重载的 operator() 的类)。但是我还不确定如何将其扩展到其他情况。 - Igor Tandetnik
2
一定要看看Antony Polukhin的这个演讲,它展示了如何不仅推断参数数量,还可以推断它们的类型。 - Arne Vogel
3个回答

7

通过重载转换运算符,您可以创建一个可以进入任何参数的对象。从那里开始,只需测试 lambda 是否可调用给定数量的这些参数,从某个任意大的数字递减。如果 lambda 在第一次尝试时(使用给定的任意大数量的参数)可调用,则可以假设它是可变参数的:

#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
}

很棒的想法,any_argument;我得记住它。 - max66

3
我使用了@yuri kilochek答案的修改版本来解决它。
我们不是从50个参数开始倒数,而是从零开始计数。当我们得到匹配时,我们知道调用lambda所需的最小参数数量。然后我们继续向上搜索,直到一个合理的最大值,看是否存在最大参数数量(当您有默认参数时可能会发生这种情况)。
如果达到参数计数限制,我们假设lambda是可变参数的。
这个实现显著减少了非可变参数lambda的模板实例化数量。它还为我们提供了所有lambda的最小参数数量和任何非可变参数lambda的最大参数数量。
再次感谢Yuri Kilochek为这个优雅的解决方案奠定了基础。查看他的答案以获取有关实现的更多详细信息。
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
};

2
请接受这个答案。此外,您的方法会错误地将带有默认参数的lambda报告为可变参数。 - yuri kilochek
1
@yurikilochek 哎呀,我忘记了默认参数-.-。我想你可以继续检查是否调用停止工作,但这样你又会得到一个神奇的“最大”数字。我还需要再考虑一下。此外,在发布问题后的前几天内,我无法接受自己的答案。 - Rick de Water
2
@yurikilochek,我已经更新了我的答案,支持默认参数。 - Rick de Water

2
我不知道如何计算泛型lambda的所有参数[编辑:但Yuri Kilochek知道如何做到:请查看他的答案以获取一个伟大的解决方案]。对于非泛型lambda,可以像Igor Tandetnik建议的那样,检测指向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,您可以检测只有可变参数列表的函数(我称之为“纯可变参数函数”),例如您的“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; }

但这并不完美,因为会出现假阳性和假阴性。

问题在于当您使用“非纯可变参数lambda”进行检查时

Original Answer翻译成"最初的回答"

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

虽然这是可变参数函数,但第一个参数不接受int,因此无法被识别为“纯可变参数函数”;不幸的是,下面的lambda表达式

Original Answer翻译成:最初的回答

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

另一个问题是,当检测到接收高于已检查数量(例如50)的参数的非变参通用lambda函数时,它们也被视为“纯虚函数”。
而且,这个解决方案无法检测出lambda_variadic2(一个非纯变参lambda)是否为变参函数。
以下是我能想到的最佳答案的完整编译示例。
#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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接