3

I was wondering if it is possible to capture an alternating parameter pattern using a parameter pack. For example,

template<typename T, size_t U, typename... Args>
class foo<T, U, Args...>
{
   public:
     foo() : my_T(nullptr), my_U(U) {}

   private:
     T* my_T;
     size_t my_U;
     foo<Args...> my_next_foo;
}

So this doesn't work since Args is a parameter pack of only types. Is there a way to modify this so that the typename T, size_t U pattern can be properly captured in a variadic template? Thanks

armstrhu
  • 303
  • 3
  • 8
  • 1
    Yeah. Take pairs of a type and a value as a type list. – Columbo May 27 '15 at 15:57
  • and in general (when your pattern is non-repeating), the common way to transfer non-type arguments is by putting them inside a class. Examples include `std::true_type`, `std::integral_constant`, and so on. – davidhigh May 27 '15 at 16:03

4 Answers4

4

Values as template parameters are second class citizens, in my experience.

Upgrade them to first class with an alias:

template<std::size_t n>
using size = std::integral_constant<std::size_t, n>;

then pattern match:

template<class...>
struct foo;
template<>
struct foo<> {
  // empty
};
template<class T, size_t U, typename... Args>
struct foo<T, size<U>, Args...> {
  foo() : my_T(nullptr), my_U(U) {}

private:
  T* my_T;
  size_t my_U;
  foo<Args...> my_next_foo;
};

and bob is your uncle.

Note, however, that taking U as a template parameter, then storing it as a run time value, is highly questionable.

Users of foo must do:

foo< Chicken, size<3>, Dog, size<17> >

instead of

foo< Chicken, 3, Dog, 17 >
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • This seems like the best option so far. One question, what would be the pitfall of storing the template parameter for use during runtime? – armstrhu May 27 '15 at 16:22
  • @armstrhu you can access them at runtime without storing them. `enum {my_U=U};` lets you access the compile-time value of `U` from the type `foo>` relatively easily. The reason why it is a bad sign is that if you have a value that varies at run time, why not pass it in as a run time value, instead of encoding it in the type? – Yakk - Adam Nevraumont May 27 '15 at 16:25
  • ahh, yeah good point. So you are correct, for my application I don't actually need to store the value of U, just access it. I guess in my little example above I didn't fully think that through. Basically, I am building a variadic template over a set of std::array's and I was trying to figure out how one could pass the array type and length. So really, my T* is actually an std::array* and there would be no size_t U variable. Your solution should solve my problem regardless. Thanks – armstrhu May 27 '15 at 16:36
3

Sure, just write your own pair:

template <typename T, size_t U>
struct foo_pair { };

And have a pack of those:

template<typename T, size_t U, typename... Pairs>
class foo<foo_pair<T, U>, Pairs...> {
    ...
};

Which would be instantiated like:

foo<foo_pair<int, 4>, foo_pair<char, 17>, ...> f;
Barry
  • 286,269
  • 29
  • 621
  • 977
0

You can pass Args to foo as an std::tuple. Use std::integral_constant for U instead of passing an integral constant as a template parameter to the std::tuple. Then the Args parameters pack contains all the pairs of types and sizes.

For example, when instantiating foo you would do so with a type like this:

Foo<std::tuple<int, std::integral_constant<size_t, 5> >,
    std::tuple<char, std::integral_constant<size_t, 3> > >

When implementing foo, you should pass your parameter pack to another std::tuple and use std::tuple_element to select the Nth element of the tuple.

This is just one possible approach, you may find it easier to implement foo if you use type lists.

bfair
  • 1,101
  • 7
  • 16
  • A problem with `tuple` this way is that it is a large, complex class, in order for instances to be optimal at run time. Using it as nothing more than a way to pack together types can cause the compiler to get creaky, needlessly. – Yakk - Adam Nevraumont May 27 '15 at 16:17
0

You might create a class representing the data container T* my_T with size_t my_U. Having that, you can implement your template:

#include <array>

using std::size_t;

template<typename... Args>
struct foo;

template<typename T, size_t N>
struct foo<std::array<T, N>>
{
    std::array<T, N> array;
};

template<typename T, size_t N, typename... Args>
struct foo<std::array<T, N>, Args...>
{
    std::array<T, N> array;
    foo<Args...> next_array;
};

int main() {
    foo<std::array<char, 1>, std::array<short, 2>, std::array<int, 4>> foos;
}

Note: I am using std::array as an replacement for the data container.