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

时间:2016-01-18 10:17:30

标签: c++ templates c++11 c++14 variadic-templates

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

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

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

5 个答案:

答案 0 :(得分:3)

如果要进行元编程,请开始使用类型。如果您需要非类型模板参数,请将它们移动到类型asap。

下面,我首先点击Pack<1,2,3>并将其转换为types< std::integral_constant<int, 1>, std::integral_constant<int, 2>, std::integral_constant<int, 3> >。这是与您的一揽子内容明显对应的类型列表。

然后,我介绍一个标签类型模板。这是一种&#34;携带&#34;另一种类型,但它本身就是无国籍。您可以从模板实例的值中提取类型作为奖励。

第三,我为每种类型写了一个&#34;&#34;获取lambda和一组类型的函数,并继续为每个类型调用lambda一次,传入一个标记类型。

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

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

结果是你可以将它注入你的代码:

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

在您的方法中间,并使用ints&#34; inline&#34;。

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

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,其列表中的每个类型都有constexpr int i

上面的一些工作将你的int列表提升为一个类型列表,因为只使用类型会使元编程变得不那么特殊。你可以跳过这个提升,并写一个for_each_integer直接用Pack<int...>代码更少的代码,但它对我来说似乎没那么有用。

答案 1 :(得分:2)

您可以向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<N>::foo调用N

正如Yakk所建议的那样,你可以传入一个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);

答案 2 :(得分:1)

如果您想拥有用于运行时迭代的可变参数包,您可以将std::array附加到struct Pack作为:

template <int...Is> struct Pack {
  std::array<int, sizeof...(Is)> arr = {{Is...}};    
};

然后迭代:

static void foo() {
  for(auto && i : ints{}.arr) std::cout << i << " ";
}

Live Demo

答案 3 :(得分:1)

你在这里写的只是很奇怪,你在哪里找到了这个实现 僵硬?

  1. 你需要一个辅助功能,这只是一个事实,你可以解决它 不知何故,但我没有看到这一点。

    目前唯一的解决方案是使用Clang 3.6,它们已经实现了 新语法,允许你写这样的东西。

    //我很确定,这是语法,它被称为折叠表达式
    //你可以在这里阅读更多相关信息:
    // http://gamedevelopment.tutsplus.com/tutorials/how-to-match-puzzle-shapes-using-bitmasks--gamedev-11759

    template<typename ... Type>
    auto sum(Type ... argument)
    {
        return (... + argument);
    }
    

    在任何其他编译器中,实现它的方法是编写两个简单的函数

    template<typename Tail>
    auto sum(Tail tail)
    {
        return tail;
    }
    
    template<typename Head, typename ... Tail>
    auto sum(Head head, Tail ... tail)
    {
        return head + sum(tail);
    }
    

    这接受任何支持+的东西,所以字符串,整数,双精度都可以     更多,但你得到了它的要点。

    你的例子看起来像这样

    template<typename Tail>
    void print(Tail tail)
    {
        cout << tail << endl;
    }
    
    template<typename Head, typename ... Tail>
    void print(Head head, Tail ... tail)
    {
        cout << head;
    
        print(tail...);
    }
    

    用法:

    print(1, 3.14, "something", string{"yeye"}, 52);
    

    sum(1, 512, 55, 91);
    

    还有一些其他方法可以使用可变参数模板,就像这里的人所描述的那样,     有太多的东西,我把它放在这里,所以我只是链接:

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html

  2. 迭代模板的参数有点困难,因为你必须使用 一些真正的编译器魔术和index_sequence。

    我有一个例子躺在这里的某个地方,因为我一直在搞乱     最近用它。

    template<typename InputTuple, std::size_t ... N>
    void tupleIteratorImpl(InputTuple& input, std::index_sequence<N...>)
    {       
        // DO WHATEVER YOU WANT HERE, but the structure is
    
        FUNCTION(/* pass all of the template parameters as arguments */, std::get<N>(input)...);
    
        // and FUNCTION has to have the structure of the examples from point 1.
        // but with this, you can already do pretty much anything you imagine
        // even at compile time
    }
    
    template<typename InputTuple, typename Indices = std::make_index_sequence<std::tuple_size<InputTuple>::value>>
    void tupleIterator(InputTuple& input)
    {
        tupleIteratorImpl(input, Indices());
    }
    

    这个功能已包含在c ++ 17中,它被称为apply,这里是文档: http://florianjw.de/en/variadic_templates.html甚至包含一些示例代码。

  3. 希望这能回答你的一些问题。

答案 4 :(得分:1)

这是我能想到的最短时间:

#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();
}