tuple-driven file read/write with variadic templates #
tuple-driven file read/write with variadic templates
Today will be something completely different. We’ll focus on C++11 feature called variadic templates. Goal of this post is providing functions which will read binary files described by tuples, for example you can describe content of the file as std::tuple and by only one call you will dump or read this tuple to file. Tuple is somehow similar to std::array with the difference that it’s capable of holding different types of data in one container.
Variadic templates
First of all we need to understand what variadic template is. You can of course start with some overview on wiki. I think the simplest usable example of variadic templates would be something like this snippet:
What is it? We have created print_many variadic function. It accepts any number of arguments of any type. We call it with 3 arguments of types ‘const char’, ‘int’ and ‘int’. Compiler matches print_many(T value, Args… args) and put ‘const char’ as T and {‘int’, ‘int’} as Args… The function is now created by the compiler. We cannot easily access elements in parameter pack (args) but we do have access to the first argument ‘T value’. We can access it as any other regular function argument. In this case we will pass it to std::cout for printing on stdout. After that we’re recursively calling print_many but this time with 2 arguments, int’s. Now T becomes int and Args becomes {‘int’}. Recursing again, T is once again int but Args is empty, printing last item and recursing again with empty parameter pack. This is why we have explicitly defined print_many() without any arguments, just to satisfy termination condition. The equivalent of the above code without variadic templates would be (this is what will be generated by compiler):
And this is how for example make_shared was implemented without variadic templates. Libraries (boost) provides make_shared overload for any number of arguments. To support N arguments you need to create N+1 functions.
Iterating through std::tuple
Next step will be to write some routines for iterating through std::tuple and printing it content. It will be quite similar to our previous example with print_many().
It’s a little bit more complicated than the first example. But let’s start from the beginning, what happen when you call print_tuple function? Compiler will match both functions so the call should be ambiguous. It is not thanks to enable_if which uses C++ feature called SFINAE. The I argument defaults to 0 which is less than sizeof…(Tp) (3 at this point) so enable_if will produce error for first overload leaving us with the second one and this is what we want. Now each call to print_tuple will cause calling second variant until we meet the I == sizeof…(Tp) condition. You can check this by changing print_tuple call in main function to print_tuple<3>(t);, it will cause the condition in first overload to be true and the second to be false. Nothing will be printed, you have just used the first function. But let’s dive into implementation of the second print_tuple, it’s two-liner. First line is just for getting an I-th element from tuple and printing it. The second line is recursive call with I+1 causing next element to be recursively printed. It will recurse until first print_tuple’s enable_if condition evaluates to true, namely until I == 3 in our case, this will terminate recursion.
Putting it all together
Only thing which is left is to provide functions for reading and writing data. They will be very similar to print_tuple but instead of writing to stdout they will read/write from a file stream.
Simple, isn’t it?;-) Looks pretty obfuscated but this is C++ meta programming. As a homework you can create a function for iterating through the tuple and applying some functor over each element. That way you can have only one iterate_over function and three functors for reading from file, writing to file and printing to stdout. No code duplication!