Variadic templates are a great new feature of C++, yet they can be a bit confusing. __VA_ARGS__ are also a nice feature of the preprocessor, yet it has a few oddities. These two features can however be combined together to get a rich and flexible macro system.
One of my uses of this is a nice debugging system. Basically I want to write the following types of statements in my code:
[sourcecode]
TRACE( object );
TRACE( "process", str_a, item );
TRACE();
[/sourcecode]
These should output a typical debug trace statement, including the location and the objects I’ve passed to the function.
Simple Approach
Ignore the last example first: the call to ‘TRACE()’ which doesn’t have any parameters. The code below will do a very basic outputting of the parameters.
[sourcecode]
template<typename TF>
void write_debug_output( std::ostream & out, TF const& f ) {
out << f << std::endl;
}
template<typename TF, typename … TR>
void write_debug_output( std::ostream & out, TF const& f, TR const& … rest ) {
out << f << " ";
write_debug_output( out, rest… );
}
#define TRACE(…) write_debug_output( std::cout, __FILE__, __LINE__, __VA_ARGS__ )
[/sourcecode]
On first exposure to variadic templates this may seem daunting, but there isn’t really much involved. The two functions create a pattern: one of the functions accepts all those calls with more than two arguments and the other matches all those with just two parameters (where the first parameter is always the output stream). The multi-parameter function calls itself in recursion, each time with one less parameter, terminated by a final call to the other function.
This simple version may well work for a lot of code. It has a few problems however. The first one is that it assumes all the parameters to be written have a stream insertion operator. It also assumes that is how we wish to write it for debugging, which is not always the case. The second problem is the one I said to ignore at first: what happens if you try to call ‘TRACE()’ without parameters? It fails due to a limitation in the way standard ‘__VA_ARGS__’ works.
Creating a class
The previous approach was also a bit limited in how we dealt with the file and line number parts. We’d like a bit more control on how everything is written. To do this, and fix the previous problems, we’ll wrap it all in a class.
[sourcecode]
#include <iostream>
#include <type_traits>
template<typename TF>
void write_debug_output( std::ostream & out, TF const& f ) {
out << f;
}
struct tracer {
std::ostream & out;
tracer( std::ostream & out, char const * file, int line )
: out( out ) {
out << file << ":" << line << ": ";
}
~tracer() {
out << std::endl;
}
template<typename TF, typename … TR>
void write( TF const& f, TR const& … rest ) {
write_debug_output( out, f );
out << " ";
write( rest… );
}
template<typename TF>
void write( TF const& f ) {
write_debug_output( out, f );
}
void write() {
//handle the empty params case
}
};
#define TRACE(…) tracer( std::cout, __FILE__, __LINE__ ).write( __VA_ARGS__ )
[/sourcecode]
In this version a temporary class is created. The constructor gets the location as parameters and format that output however it wishes. This isn’t done strictly for the formatting. We’ve also isolated the ‘__VA_ARGS__’ into a function call all on its own. This is how one solves the problem of calling ‘TRACE’ with no parameters: we don’t have a trailing comma when done like this. We of course need the matching ‘write()’ function which takes no parameters.
Overriding the output
Instead of directly writing out the various parameters, ‘write_debug_output’ is called. This allows us to write different output functions for specific parameter types. Say we have a class ‘my_object’ and wish to override the default output, or perhaps it doesn’t even have stream insertion. Just write a version of ‘write_debug_output’ taking ‘my_object’ as the type.
[sourcecode]
struct my_object { };
void write_debug_output( std::ostream & out, my_object const & f ) {
out << "**Mine**";
}
int main() {
TRACE(my_object());
}
[/sourcecode]
This works because this version of the ‘write_debug_output’ function is preferred to the generic template version. Now a special debug formatter can be written for any type. We don’t have to modify the stream operator, nor touch the class in any way.
Conclusion… sort of
The above gives a complete example of a working debug information system. It makes use of variadic templates and __VA_ARGS__. It solves the problem of trailing commons by isolating __VA_ARGS__ into a function call on its own. It also provides a clean way to format the file name and line number and provide type specific overrides.
You can see a working example here.
I confess the above is kept simple because we’re using ‘const &’ parameters. It works fine for debugging information, but what if you need to support mutable types, or a combination of modifiers? Refer back to my article on the universal reference to understand how tricky it is. I’ve put together a complete example.