【问题标题】:Arity of a generic lambda通用 lambda 的 Arity
【发布时间】:2014-10-31 11:41:30
【问题描述】:

可以通过访问其operator() 来推断非泛型 lambda 的数量。

template <typename F>
struct fInfo : fInfo<decltype(&F::operator())> { };

template <typename F, typename Ret, typename... Args>
struct fInfo<Ret(F::*)(Args...)const> { static const int arity = sizeof...(Args); };

这对于[](int x){ return x; } 之类的东西来说很好,因为operator() 没有模板化。

但是,通用 lambda 会模板化 operator(),并且只能访问模板的具体实例 - 这有点问题,因为我无法手动为 operator() 提供模板参数,因为我没有知道它是什么。

所以,当然,像

auto lambda = [](auto x){ return x; };
auto arity = fInfo<decltype(lambda)>::arity;

没用。

我不知道要转换成什么,也不知道要提供什么模板参数(或多少)(operator()&lt;??&gt;)。
任何想法如何做到这一点?

【问题讨论】:

  • 通用 lambda 的 operator() 可以是可变参数模板。对于这些,你对 arity 的定义是什么?
  • 你为什么要问arity,而不是'你能接受多少ints?'?

标签: c++ c++14 generic-lambda


【解决方案1】:

这种技术在某些情况下会起作用。我创建了一个 fake_anything 类型,它几乎可以伪造任何东西,并尝试使用一些实例来调用你的 lambda。

#include <iostream>

struct fake_anything {
  fake_anything(fake_anything const&);
  fake_anything();
  fake_anything&operator=(fake_anything const&);
  template<class T>operator T&() const;
  template<class T>operator T&&() const;
  template<class T>operator T const&() const;
  template<class T>operator T const&&() const;
  fake_anything operator*() const;
  fake_anything operator++() const;
  fake_anything operator++(int) const;
  fake_anything operator->() const;
  template<class T>fake_anything(T&&);
};
fake_anything operator+(fake_anything, fake_anything);
fake_anything operator-(fake_anything, fake_anything);
fake_anything operator*(fake_anything, fake_anything);
fake_anything operator/(fake_anything, fake_anything);
// etc for every operator

template<class>using void_t=void;
template<class Sig, class=void>
struct can_invoke:std::false_type{};
template<class F, class...Args>
struct can_invoke<F(Args...),
  void_t< decltype( std::declval<F>()( std::declval<Args>()... ) ) >
> : std::true_type
{};

template<class Sig>struct is_sig:std::false_type{};
template<class R, class...Args>struct is_sig<R(Args...)>:std::true_type{};

template<unsigned...>struct indexes{using type=indexes;};
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};
template<unsigned max>using make_indexes_t=typename make_indexes<max>::type;

template<class T,unsigned>using unpacker=T;

template<class F, class A, class indexes>
struct nary_help;
template<class F, class A, unsigned...Is>
struct nary_help<F,A,indexes<Is...>>:
  can_invoke<F( unpacker<A,Is>... )>
{};
template<class F, unsigned N>
struct has_n_arity:
  nary_help<F, fake_anything, make_indexes_t<N>>
{};

template<class F, unsigned Min=0, unsigned Max=10>
struct max_arity{
  enum{Mid=(Max+Min)/2};
  enum{
    lhs = max_arity<F,Min,Mid>::value,
    rhs = max_arity<F,Mid+1,Max>::value,
    value = lhs>rhs?lhs:rhs,
  };
};
template<class F, unsigned X>
struct max_arity<F,X,X>:
  std::integral_constant<int, has_n_arity<F,X>::value?(int)X:-1>
{};

template<class F, unsigned Min=0, unsigned Max=10>
struct min_arity{
  enum{Mid=(Max+Min)/2};
  enum{
    lhs = min_arity<F,Min,Mid>::value,
    rhs = min_arity<F,Mid+1,Max>::value,
    value = lhs<rhs?lhs:rhs,
  };
};
template<class F, unsigned X>
struct min_arity<F,X,X>:
  std::integral_constant<unsigned,has_n_arity<F,X>::value?X:(unsigned)-1>
{};

auto test1 = [](auto x, auto y)->bool { return x < y; };
auto test2 = [](auto x, auto y) { return x + y; };
auto test3 = [](auto x) { return x.y; };

int main() {
  std::cout << can_invoke< decltype(test1)( fake_anything, fake_anything ) >::value << "\n";
  std::cout << can_invoke< decltype(test1)( int, int ) >::value << "\n";
  std::cout << has_n_arity< decltype(test1), 2 >::value << "\n";
  std::cout << max_arity< decltype(test1) >::value << "\n";
  std::cout << max_arity< decltype(test2) >::value << "\n";
  // will fail to compile:
  // std::cout << max_arity< decltype(test3) >::value << "\n";
}

live example.

注意足够的 SFINAE 将意味着上述结果将得到错误的结果,如使用 operator.,或在某些“派生”类型上使用 operator.,或访问基于 fake_anything 参数的类型等。

但是,如果 lambda 使用 -&gt;X 子句指定其返回值,那么 fake_anything 就足够了。最困难的部分是处理身体。

请注意,这种方法通常不是一个好主意,因为如果您想知道函数的数量,您可能还知道要调用函数对象的事物的类型!上面我很容易回答这个问题(可以用这些参数调用这个函数对象吗?)。甚至可以改进询问“可以调用此函数对象的这些参数的最长/最短前缀是什么”,或处理“调用此函数对象的 X 类型重复次数”(如果您想要彻底失败,您需要一个上限)。

【讨论】:

  • 当 lambda 的主体尝试在 fake_anything 不支持的上下文中使用 auto 参数时,似乎不起作用,例如return x*2;return x.get();;因为编译器在 can_invoke&lt;T&gt; 中评估主体
  • @piotrs。啊,是的,必须使用基于身体的类型扣除。对于-&gt;,它应该没有理由这样做,但这仍然很糟糕。即使我覆盖了每个运算符,仍然有.
  • @piotrs。一些改进。它适用于x*2,但不适用于x.get(),如果xauto 类型。
  • 我必须注意,VS2015 在大多数情况下给出了错误的结果。这是输出:0 0 0 -1 2
  • @tower120 在 MSVC2015 中使用 decltype 会让人失望。
【解决方案2】:

这是不可能的,因为函数调用运算符可以是可变参数模板。对于一般的函数对象来说,永远不可能做到这一点,而特殊情况的 lambdas 因为它们碰巧没有同样强大,所以总是一个坏主意。现在是时候让这个坏主意回家了。

【讨论】:

    【解决方案3】:

    这是一个 c++17 解决方案,适用于泛型和可变参数 lambda 以及具有可变参数模板 operator() 的函子。这个想法是用递减的参数数量递归模拟调用,并在找到第一个匹配数量的参数时使用 SFINAE 中断递归。它在 gcc >= 7 和 Clang >=5 上编译。可以在here 找到一个工作示例。

    #include<utility>
    
    constexpr size_t max_arity = 10;
    
    struct variadic_t
    {
    };
    
    namespace detail
    {
        // it is templated, to be able to create a
        // "sequence" of arbitrary_t's of given size and
        // hece, to 'simulate' an arbitrary function signature.
        template <size_t>
        struct arbitrary_t
        {
            // this type casts implicitly to anything,
            // thus, it can represent an arbitrary type.
            template <typename T>
            operator T &&();
    
            template <typename T>
            operator T &();
        };
    
        template <typename F, size_t... Is,
                    typename U = decltype(std::declval<F>()(arbitrary_t<Is>{}...))>
        constexpr auto test_signature(std::index_sequence<Is...>)
        {
            return std::integral_constant<size_t, sizeof...(Is)>{};
        }
    
        template <size_t I, typename F>
        constexpr auto arity_impl(int) -> decltype(test_signature<F>(std::make_index_sequence<I>{}))
        {
            return {};
        }
    
        template <size_t I, typename F, typename = std::enable_if_t<(I > 0)>>
        constexpr auto arity_impl(...)
        {
            // try the int overload which will only work,
            // if F takes I-1 arguments. Otherwise this
            // overload will be selected and we'll try it 
            // with one element less.
            return arity_impl<I - 1, F>(0);
        }
    
        template <typename F, size_t MaxArity = 10>
        constexpr auto arity_impl()
        {
            // start checking function signatures with max_arity + 1 elements
            constexpr auto tmp = arity_impl<MaxArity + 1, F>(0);
            if constexpr (tmp == MaxArity + 1)
            {
                // if that works, F is considered variadic
                return variadic_t{};
            }
            else
            {
                // if not, tmp will be the correct arity of F
                return tmp;
            }
        }
    }
    
    template <typename F, size_t MaxArity = max_arity>
    constexpr auto arity(F&& f) { return detail::arity_impl<std::decay_t<F>, MaxArity>(); }
    
    template <typename F, size_t MaxArity = max_arity>
    constexpr auto arity_v = detail::arity_impl<std::decay_t<F>, MaxArity>();
    
    template <typename F, size_t MaxArity = max_arity>
    constexpr bool is_variadic_v = std::is_same_v<std::decay_t<decltype(arity_v<F, MaxArity>)>, variadic_t>;
    

    用法:

    auto l = [](auto...){};
    static_assert(is_variadic_v<decltype(l)>);
    

    和:

    auto l = [](auto, auto, auto){};
    static_assert(!is_variadic_v<decltype(l)>);
    static_assert(arity(l) == 3);
    

    【讨论】:

    • 4 年后,我们有了一种解决方法,它的效果只有旧解决方法的一半。什么进步,C++!
    • 这会将类型转换为任意类型,因此如果想要使用 arity_v,则必须将它们转换回 lambda 中。
    【解决方案4】:

    我会说这是部分可能的,至少当您显式实例化operator() 的自动参数时,您可以知道整体数量(模板+常规类型) :

    template <typename F, typename... Args>
    struct autofInfo : fInfo<decltype(&F::template operator()<Args...>)> {};
    
    auto lambda = [](auto x, int y, float z) { return x + y + z; };
    
    auto arity = autofInfo<decltype(lambda), int>::arity;
    //                                       ^^^ list of auto parameters instantiations
    
    assert(3 == arity);
    

    【讨论】:

      【解决方案5】:

      另一种可能的解决方案,在可能的模板类型已知的情况下: http://coliru.stacked-crooked.com/a/e3a07d723a8f27e9

      using T1 = string;
      using T2 = int;
      
      std::integral_constant<int, 1> static arity(function<void(T1)>){ return {}; }
      std::integral_constant<int, 2> static arity(function<void(T1, T2)>){ return {}; }
      
      template<class Fn>
      using Arity = decltype(arity(Fn{}));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多