Reputation: 123
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
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, "");
}
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