6

In the little program below, I show the solution I currently use to extract the template arguments of a class and iterate over it via a recursive helper function.

I wonder if there is a more concise way to do it, as I explain in the pseudo-code in the comments below.

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();
}
Fabio
  • 2,105
  • 16
  • 26
  • 1
    1) is impossible; 2) is [straightforward](http://stackoverflow.com/a/25683817/2756719). – T.C. Jan 18 '16 at 10:30
  • Thanks @T.C. I voted your answer at the given link, as it is interesting on its own. However I am not sure this applies here: that expands function arguments, whereas here I have template arguments. Also, It is not clear to me however why (void,0) resolves to int. – Fabio Jan 18 '16 at 13:53
  • `(void, 0)` uses the funny comma operator which just evaluates to it's right argument. – Rumburak Jan 18 '16 at 22:02
  • 1
    The type of the pack is irrelevant. As long as you have a pack, you can unpack it by a pack expansion. – T.C. Jan 19 '16 at 03:38

5 Answers5

3

If you want to do metaprogramming, start working in types. If you want non-type template parameters, move them over to types asap.

Below, I first take Pack<1,2,3> and convert it to types< std::integral_constant<int, 1>, std::integral_constant<int, 2>, std::integral_constant<int, 3> >. This is a list of types that is in obvious correspondence to your pack of ints.

Then, I introduce a tag type template. This is a type which "carries" another type, but it itself is stateless. You can extract the type from an value of an instance of the template as a bonus.

Third, I write a "for each type" function that takes a lambda and a pack of types, and proceeds to call the lambda once for each of the types, passing in a tag type.

In the body of the lambda, we can extract the passed type by using decltype on the tag variable (or a helper macro).

We chain those together, and from the passed tag type we can extract the integer in the original pack.

The result is you can inject this into your code:

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

in the middle of your method, and work on the ints "inline".

If we wanted to only solve your specific problem, we'd do a bit less boilerplate, but I like the genericness.


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

now we can do:

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

and pack is a list of types, not a list of integers.

Now some hana-style metaprogramming: (metaprogramming with values instead of pure types)

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

which lets you iterate over a collection of types.

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

gives you a lambda that has a constexpr int i for each of the types in your list.

A bunch of the above work lifts your list of ints into a list of types, because working with only types makes metaprogramming less special-case. You can skip that lifting, and write a for_each_integer that takes a Pack<int...> directly with less code, but it seems less useful to me.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

You could add a foo_for_each function to Pack:

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

Then you would just write:

ints::foo_for_each<B>();

This will call B<N>::foo for each N in the pack.

As suggested by Yakk, you could pass in a lambda which gets a tag type as an argument to create a generic 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)... 
        } ;
    }
};

Then you could call like this:

auto call_foo = [](auto tag) { type_t<decltype(tag)>::foo(); };
ints::for_each<B>(call_foo);
TartanLlama
  • 63,752
  • 13
  • 157
  • 193
1

If you want to have the variadic pack for runtime-iteration you could attach a std::arrayto your struct Pack as:

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

And then iterate through as:

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

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168
1

What you wrote here is just plain weird, where did you even find an implementation this rigid ?

  1. You NEED a helper function, it's just a fact, you could probably work around it somehow, but I do not see the point of that.

    The only solution for that, right now, is to use Clang 3.6, they already implemented the new syntax, that allows you to write something like this.

    // I am pretty sure, this was the syntax, it's called a fold expression
    // you can read more about it here:
    // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html

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

    In any other compiler, the way to go about it, is to write two simple functions

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

    This accepts anything that supports + so strings, ints, doubles will work, probably some more, but you get the gist of it.

    Your example would look like this

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

    Usage:

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

    or

    sum(1, 512, 55, 91);
    

    There are some other ways to use variadic templates, like this guy here describes, there is way too much of it, for me to put it here, so I'll just link:

    http://florianjw.de/en/variadic_templates.html

  2. Iterating over the arguments of a template is a bit harder, because you have to use some real compiler magic and index_sequence.

    I have an example lying around here somewhere, because I have been messing around with it lately.

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

    A function for this is already included in c++17, and it is called apply, here's the documentation: http://en.cppreference.com/w/cpp/experimental/apply with some sample code even.

Hope this answers some of your questions.

1

This is the shortest I can come up with:

#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();
}
Rumburak
  • 3,416
  • 16
  • 27
  • Ramburak, this is the solution which fits me best, as I have two set of indices to unpack in parallel. Now I will have to fit it with the lambda suggestion from above. Thanks. I do not understand the need to cast to void (it compiles anyway). Instead of int[], it seems to work also with _template swallow(T&&...) {}_. Why the 0 in (xxx,0)? – Fabio Jan 19 '16 at 06:58
  • The cast to void gets rid of a compiler warning about swallow instance being unused. The 0 in (xxx,0) turns that comma expression into an int, regardless of the type of xxx (see: comma operator). Read this http://en.cppreference.com/w/cpp/language/eval_order about order of evaluation. As far as I understand, the order of evaluation for function calls is guaranteed in the case of the initializer list, but not for the constructor (and/or there was a bug in gcc with respect to that). – Rumburak Jan 19 '16 at 07:50