When writing code which relies heavily on templates for choosing functionality at compile time (e.g. using Policy-Based Design), it is important to find a sweet spot between flexibility and expressibility. These techniques can often be highly generic, but incredibly verbose and syntactically noisy. Consider the following:
This class has three template parameters to select specialised code for certain types. I’ve limited it to three for the sake of brevity, but there could be many more parameters than this. If client code just wants to use the defaults, then the code is very simple:
It’s also easy to specify arguments for all parameters, or for the first couple:
But what if we want to set just the last argument:
In the above example, we still need to specify the first two arguments, even though we just want the defaults. This is a maintenance issue even with just three parameters, as all these uses of the template must be changed if the default arguments change. With more than three arguments it’s even worse, and needlessly verbose to boot. What we would like is a flexible, generic, terse way to selectively change default arguments. Read on for a few possible solutions.
One option would be to use a dummy tag to stand in for the default, then choose the real template argument value based on whether it is equal to that dummy or not.
Easy to understand and implement.
Options<> and Options<int, long, std::string> are different types.
use_default has to be repeated quite a lot.
Can’t tell the default arguments by looking at the template declaration.
Requires significantly altering the class
Manual member aliases
Another option is to provide member alias templates which handle modifying the template arguments.
Very terse usage.
Default arguments are in the declaration.
Requires significantly altering the class.
Need to repeat the other arguments in all the WithX alias templates.
Don’t touch my class
It would be good to have a solution which doesn’t require heavily altering the class (especially when there are many parameters). The following code is pretty complex, but does the job (you could use your favourite metaprogramming library to make it more simple).
Doesn’t require changing the class.
Syntax isn’t as nice as the previous solution.
What we really want is a solution which combines the advantages of the above two options. We can achieve this by wrapping with_n in a template class and inheriting from it.
Adding support only requires inheriting from a type.
Traits don’t have descriptive names.
Requires modifying the class.
If you find yourself needing this functionality, hopefully one of the above solutions works for you. Feel free to suggest others in the comments below.
Let me know what you think of this article on twitter @TartanLlama or leave a comment below!