Saturday, November 23, 2013

Fun with C++11 variadic templates

The other day I decided to experiment a bit with the support for variadic template support in C++11 (the new C++ standard). Like many C++ features, they're incredibly powerful, but not always that easy to use. After some experimentation I figured out how to do what I wanted, and I thought I'd write it up in case anyone else wanted to try to do the same thing.

I wanted to create a fixed-size vector class (similar to std::array), but with a constructor that accepts the appropriate number of arguments. So for example, Vec should have a constructor that accepts 3 floats.

The simplest solution is to use initializer lists, which allow a constructor to take an arbitrary sequence of arguments of the same type. However, this has a number of disadvantages:
  1. It can only be used with brace-initializer syntax, not parentheses.
  2. It does not enforce the correct length at compile time.
Variadic templates allow a function to take a variable number of arguments. My second attempt at a constructor started by looking something like this:

template Vec(Args&&... args);

This is just a constructor that accepts an arbitrary set of arguments, so it doesn't seem to fix the second problem, and additionally it doesn't even require the types to be correct. We can fix that using SFINAE to statically validate the arguments before the template is instantiated. The tricky part is figuring out where to insert the enable_if: the constructor has no return type, so it can't go there; and we can't add a default argument, because that makes the interpretation of the variadic argument list ambiguous. However, we can add an extra template parameter with a default value:

template
         typename E = typename enable_if_all
             std::is_convertible::value...>::type>
Vec(Args&&... args);

Here enable_if_all is a variadic version of enable_if, which has a type member if all its arguments are true. It's not part of the standard, so I had to implement it myself. I won't go into details since this isn't the solution I used. It works, but it's roundabout and I'm not convinced that it won't differ in some subtle ways from a constructor that actually accepts only type Twhen in the presence of other constructors (because every parameter will match exactly, where as a constructor with float parameters might require a conversion sequence which would make this a worse match than another constructor).

In the end I realised that the solution was to create a template parameter pack with N copies of T. At first I thought this couldn't be done, because parameter packs are not types and so one cannot typedef one in a helper class to be expanded in the constructor. The solution is to create it in the vector class itself, using recursion to generate the appropriate type. You can see the code here. The VecBase class is templated with the type T, followed by N copies of T as a variadic parameter pack. Thus, for example, VecBase is a vector with 3 ints. This makes it easy to write the constructor, since we just expand the parameter pack.

The tricky part is that we really want to write Vec rather than VecBase. This is where VecHelper comes in: it recursively adds copies of T to the type name. Thus, VecHelper::type is typedefed to VecHelper::type, which in turn is typedefed to VecHelper and finally VecHelper. This is the base case that is handled in the partial specialization at line 29 to be VecBase.

Finally, we make use of the new using feature to typedef Vec to be VecHelper::type, and the problem is solved.

No comments: