Alias templates with partial specialization, SFINAE and everything
by Malte Skarupke
Alias templates are a new way to do typedefs in C++11. You have probably seen them by now, but as a reminder here is what the standard considers to be an alias-declaration:
template<typename T> using MyVector = std::vector<T, MyAllocator<T>>; // alias-declaration MyVector<T> myvector; // instantiating with MyAllocator;
So that’s cool. Unfortunately the standard also says that “Because an alias-declaration cannot declare a template-id, it is not possible to partially or explicitly specialize an alias template.” (Paragraph 14.5/3) But that would be a terribly useful thing to have.
So why would you want that? I stumbled across my use case when I was doing some vector math that dealt with different vector sizes. Following this advice I templatized my vector type on the number of elements:
template<size_t Size, typename T> struct Vector { T elements[Size]; //... Vector operator*(const T & rhs) const { Vector copy(*this); for (T & element : copy.elements) { element *= rhs; } return copy; } //... }; typedef Vector<2, float> Vec2f; typedef Vector<3, float> Vec3f;
This works well until you do something where you end up with a Vector<1, float>. Which is really just a scalar. Dealing with that was annoying because my scalar multiplication was defined for floats, not for Vector<1, float>, even though they are the same thing mathematically. To get my code to compile I would either have to define my multiplication operator above twice, or I would have to add a conversion operator to Vector<1, float>. Both of which I didn’t want to do, because really I wanted Vector<1, float> to be the same as just float. Meaning I wanted
static_assert(std::is_same<Vector<1, float>, float>::value, "a scalar is a scalar");
So this is where I wanted an alias-declaration with a partial specialization. Which the standard forbids. Turns out you can solve that by adding a layer of indirection. This is my solution:
namespace detail { template<size_t Size, typename T> struct Vector { // the code from above goes here }; template<size_t Size, typename T> struct VectorTypedef { typedef Vector<Size, T> type; }; template<typename T> struct VectorTypedef<1, T> { typedef T type; }; } template<size_t Size, typename T> using Vector = typename detail::VectorTypedef<Size, T>::type;
Which is exactly what I wanted. If you think about this, this actually makes templates more powerful than they were in C++03. In C++03 you could create templates that were completely different structs depending on the template arguments,like std::vector<bool> vs. std::vector<unsigned char>, but the classes would always have to be related. You could never have an unrelated type from a library or a fundamental type behind a template. Now you can.
Enjoy.
P.S. about that vector math library article I linked above. I can’t link to it without voicing one disagreement: I agree with it for the most part, except about the SIMD thing. I have my Vector<4, float> specialized to use a __m128. (not using the trick above, but it’s a struct with an __m128 as a member) This is faster because I store almost everything using __m128. As an example I store world matrices and AABBs using Vector<4, float> which uses __m128. If you do it like that you do get a 4x speed up for many operations like his “transform AABB” example. Or the assignment operator. Which gets called very often and which nobody would spend time on optimizing, not even an auto vectorizer. All you have to do is keep all your data always aligned. And you can do that by changing your operator new() to return 16 byte aligned memory for any allocation over 8 bytes in size.
Thanks for the article! Could you also comment on why using e.g. Eigen3 is not enough for your application?
There is no real motivation behind writing my own vector library. The initial reason was that I started writing a vector library for a school project which needed vector math but didn’t allow use of libraries. And I’ve been growing that library ever since and never found a reason to switch.
Also I should say that I only use this for small personal side projects. And there it’s easier to just copy+paste my few files that have no dependencies than it is to set up a library.
This is actually the first time that I am writing code where I have to deal with differently sized vectors, which is why I started templatizing on the size. So maybe this should finally be my motivation to check out what linear algebra libraries exist out there.