Sebastian Schneider
Sebastian Schneider

Reputation: 123

Variadic templates of types, integral constants and template template parameters

I've the following problem. I have some classes that perform a mapping of an input array to an output array. I want to have the float type, as well as the lengths of the arrays as template parameters, so the mapping classes look like this:

template <typename FloatType, std::size_t input, std::size_t output>
class Mapper
{};

template <typename FloatType, std::size_t input, std::size_t output>
class FirstMapper : public Mapper<FloatType, input, output>
{};

template <typename FloatType, std::size_t input, std::size_t output>
class SecondMapper : public Mapper<FloatType, input, output>
{};

So far so good. My goal is to write a class that stacks different instances of these Mapper classes. I'd like to be able to write code like this:

StackedMapper<
               double,       // the FloatType, obviously
               input_1,      // the size of the first mapper's input array
               FirstMapper,  // the template template type of the first mapper
               input_2,      // the size of the first mapper's output and
                             // second mapper's input array
               SecondMapper, // the template template type of the second mapper
               input_3,      // the size of the second mapper's output and
                             // third mapper's input array
               FirstMapper,  // the template template type of the third mapper
               output        // the size of the third mapper's output array
               // ... any additional number of Mapper classes plus output sizes
             > stacked_mapper;

Internally, the StackedMapper class should store the mapper instances in a std::tuple. I'd expect the tuple to have the following type:

std::tuple<
            FirstMapper<double, input_1, input_2>,
            SecondMapper<double, input_2, input_3>,
            FirstMapper<double, input_3, output>
            // ...
          >;

As indicated with the ellipsis, I'd like to add an arbitrary number of Mapper classes. As you might have seen from the comments, the output size of one layer is equal to the input size of the next layer. The float type will be defined only once for all mappers in the stack.

Does anybody have an idea? I've seen this question, which solves the alternating types (integral constant and type) problem, however it doesn't seem to work with template template parameters, since I always get an error like expected a type, got 'FirstMapper'.

Does anyone have an idea on this?

Upvotes: 2

Views: 974

Answers (1)

TemplateRex
TemplateRex

Reputation: 70516

Here's a brief introduction to template-metaprogramming based on Boost.MPL. The essence is to use classes for everything in order to get as much regularity in your code as possible.

First, use an integral_constant to wrap constants. This is called "metadata" and the value is contained as a nested data member value.

// nullary metafunction ("metadata"), wrap "value"
template<class T, T v>
struct integral_constant
{
    using type = integral_constant<T, v>;
    using value_type = T;
    static constexpr auto value = v;
};

You can use classes, including integral constant metadata, as arguments to "metafunctions": regular class templates that return their value as a nested type called type.

// regular metafunction: class template that takes metadata "X", returns "type" with "value" squared
template<class X>
struct square
:
    integral_constant<typename X::value_type, (X::value * X::value)>  
{};

In order to avoid template-template parameters when passing around metafunctions, you use metafunction classes: those are regular classes that contain a nested metafunction apply

// higher-order metafunction: class that has nested metafunction "apply" which returns square
struct square_f
{
    template<class X>
    struct apply
    :
        square<X>
    {};
};

To see the usefullness of the above definitions, it's very straightforward to compute the square and fourth-power of an integral number 2 by applying the square_f metafunction class twice on an integral_constant<int, 2>

// regular metafunction that takes higher-order metafunction "F" and metafunction "X" and returns "F<F<X>>"
template<class F, class X>
struct apply_twice
:
    F::template apply<typename F::template apply<X>::type>
{};

template<class X>
struct quartic
:
    apply_twice<square_f, X>
{};

int main()
{
    using two = integral_constant<int, 2>;
    static_assert(4 == square<two>::value, "");        
    static_assert(16 == quartic<two>::value, "");
}

Live Example.

To generalize this to variadic template parameters, just use

template<class... Xs>
struct some_fun;

for metafunctions taking a variadic number of arguments. This is left as an exercise. The main point is to treat every (data, class, function) argument uniformly as a class by suitable wrappers.

Note: I use inheritance to automatically embed the nested type inside derived classes. This technique is called "metafunction forwarding" and reduces the amount of typename F<T>::type clutter.

Upvotes: 2

Related Questions