提问者:小点点

提取类的模板参数并遍历它们的最紧凑方法是什么?


在下面的小程序中,我展示了我目前用于提取类的模板参数并通过递归助手函数迭代它的解决方案。

我想知道是否有更简洁的方法,正如我在下面评论中的伪代码中解释的那样。

template <int...Is> struct Pack {};

template <int I> struct B
{
    static void foo() { std::cout << I << "\n"; }
};

// recursive helper function, also used to extract the parameter pack arguments
template <int I, int...Is>
void foo_helper( Pack<I, Is...>&& )
{
    B<I>::foo();
    foo_helper( Pack<Is...>{} );
}

// terminate recursion
void foo_helper( Pack<>&& ) {}

struct A
{
    typedef Pack<1,3,5> ints;

    static void foo()
    {
        // this is what I do
        foo_helper(ints{});

        // this is what I would like to do, ideally in one single line
        // 1) extract the template arguments pack from ints, without creating an helper function for that
        // 2) iterate on the template arguments of the pack without a recursive helper
        // In pseudocode, something like:
        // (B<IterateOver<ArgumentsOf<ints>>>::foo());
    }
};

int main()
{
    A::foo();
}

共3个答案

匿名用户

如果您想进行元编程,请开始使用类型。如果您想要非类型模板参数,请尽快将它们移至类型。

下面,我先拿

然后,我引入一个标记类型模板。这是一种“携带”另一种类型的类型,但它本身是无状态的。您可以从模板实例的值中提取类型作为奖励。

第三,我编写了一个“for each type”函数,它接受一个lambda和一个类型包,然后继续为每种类型调用一次lambda,传入一个标记类型。

在 lambda 的主体中,我们可以通过在标签变量(或辅助宏)上使用 decltype 来提取传递的类型。

我们将它们链接在一起,并从传递的标签类型中提取原始包中的整数。

结果是您可以将其注入到代码中:

for_each_type( [&](auto tag){
  constexpr int i = TAG_TYPE(tag){};
  // use i
}, ints_as_types_t<ints>{} );

在方法的中间,“内联”处理int。

如果我们只想解决您的特定问题,我们会少做一些样板,但我喜欢通用性。

template<class...>struct types{using type=types;};

template <int...Is> struct Pack {};

template<class pack> struct ints_as_types;
template<class pack>
using ints_as_types_t=typename ints_as_types<pack>::type;

template<class T, template<T...>class pack, T...ts>
struct ints_as_types<pack<ts...>> {
  using type=types<std::integral_constant<T,ts>...>;
};

现在我们可以做:

using pack = ints_as_types_t<Pack<1,2,3>>;

pack是类型列表,而不是整数列表。

现在一些 hana 风格的元编程:(使用值而不是纯类型的元编程)

template<class T>struct tag_t{using type=T; constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag={};
template<class Tag>using type_t=typename Tag::type;
#define TAG_TYPE(...) type_t<std::decay_t<decltype(__VA_ARGS__)>>;

template<class F, class...Ts>
void for_each_type(F&& f, types<Ts...>) {
  using discard=int[];
  (void)discard{ 0, ((
    f(tag<Ts>)
  ),void(),0)...};
}

这使您可以循环访问类型集合。

for_each_type( [&](auto tag){
  constexpr int i = TAG_TYPE(tag){};
  // use i
}, ints_as_types_t<ints>{} );

为您提供一个 lambda,该 lambda 对于列表中的每种类型都有一个 constexpr int i

上面的一系列工作将您的int列表提升为一个类型列表,因为只使用类型使得元编程不那么特殊。您可以跳过该提升,并为_each_integer编写一个

匿名用户

您可以将foo_for_each函数添加到Pack

template <int...Is> struct Pack {    
    template <template <int> class T>
    static void foo_for_each () {
        std::initializer_list<int> { (T<Is>::foo(),0)... } ;
    }
};

然后你会写:

ints::foo_for_each<B>();

这将调用B

正如 Yakk 所建议的那样,您可以传入一个 lambda,该 lambda 获取标签类型作为参数来创建通用 Pack::for_each:

template <typename T> struct tag { using type = T; };
template <typename T> using type_t = typename T::type;

template <int...Is> struct Pack {    
    template <template <int> class T, typename Func>
    static void for_each (Func&& func) {
        std::initializer_list<int> { 
          ((std::forward<Func>(func)(tag<T<Is>>{}))  0)... 
        } ;
    }
};

那么你可以这样打电话:

auto call_foo = [](auto tag) { type_t<decltype(tag)>::foo(); };
ints::for_each<B>(call_foo);

匿名用户

这是我能想到的最短的:

#include <iostream>

template<int... Is>
struct Pack;

template <int I> struct B
{
    static void foo() { std::cout << I << "\n"; }
};

template<typename PACK> struct unpack;

template<int...Is>
struct unpack<Pack<Is...>>
{ 
  template<template<int> class T>
  static void call()
  { 
    using swallow = int[sizeof...(Is)];
    (void) swallow{(T<Is>::foo(), 0)...};
  }
};

struct A
{
    typedef Pack<1,3,5> ints;

    static void foo()
    {
      unpack<ints>::call<B>();
    }
};

int main()
{
    A::foo();
}