With this kind of template programming, I can construct my compressor with an input object of any class that has a getByte() method, and an output object of any class that has a putByte() method.

As a library writer, I am going to go ahead and provide a convenience class that takes an std::ostream object and implements the needed function. My template class, output_bytes<T>, is defined for std::ostream only. I include a default implementation that will cause a compiler error if you attempt to construct it with some other type of object  this is a circa 1998 attempt to perform compile-time assertions.

With that in place, I can now pass in ostream objects to my compressor without the user having to implement a shim class. I use a helper function to deal with some of the mechanics needed to actually construct the compressor and run it:

So what's going on here? My template class is specialized for std::ostream, and std::ofstream is an std::ostream object, is it not? No, it isn't. As OOP programmers, we casually say that an std::ofstream object is anstd::ostream object, but this is syntactic shorthand for a deeper OOP principle. At the surface level, these are two separate classes, quite distinct from one another.

And therein lies the problem  template instantiation is simple-minded: When you create a specialized version of class output_bytes for std::ostream, the compiler will construct it for that one class, and that one class only.

This means that my convenience class output_bytes<std::ostream> is not quite as convenient as I would like it to be. Anyone using it for class derived from ostream will have to upcast their arguments:

The Enable-If Idiom

This should be a problem I can fix. What I want to do is modify my template definition of output_bytes so that it works with std::ostreamor any class derived from it.

This can be done with the help of two relatively new classes. std::enable_if is a template class that was added to the library with C++11, and std::is_base_of is a struct added with TR1 in 2007. I'll show the new definition below, then go on to try to analyze what is happening:

Comparing this code with what was seen in the 1998 template version, you'll notice that the only real changes are in the part of the class definition in which template parameters are declared.

First, the base class now has two parameters: the first being a type that will nominally be a class derived from std::ostream, and the second being an arbitrary type parameter called Enable, which defaults to type void. When instantiating objects of class output_bytes, I'm still going to just use the single type specifier, as shown below.

In the specialization of output_bytes that I have created for classes derived from std::ostream, you'll see that I am using an expression for this second parameter:

enable_if<is_base_of<ostream, T>::value>::type

This expression evaluates to a type of void if T is type std::ostream or a class derived from it. If T does not match up this way, then the expression is not defined.

SFINAE

So, in some cases a template parameter is defined, and in other cases, it is not defined. Let's see how this works. The inner part of the expression is:

is_base_of<ostream, T>::value

If you look up std::is_base_of, you'll see that the instantiation of that struct has a constant static member named value, which is true if T is derived from std::ostream (as in this case), and false if it is not. Since this is a static constant member of the struct, it is known at compile time and can be used as a template argument.

This means that we are instantiating std::enable_if with a constant that is either true or false. This is where things get interesting. We are passing std::enable_if<T>::value as the second argument to output_bytes<T,Enable>. Looking at the definition for enable_if, you'll see that if the argument passed to it is true, it has a typedef for value. If the template argument is false, the typedef does not exist.

So if T is the desired std::ostream derived class, we are passing in two type arguments to the template definition. But if T is some other unwanted class, the second argument doesn't exist. What happens then?

It turns out that C++ has a very specific rule in place for this scenario: Substitution Failure Is Not an Error, or SFINAE. What this means is that in this case, the compiler finds that it can't provide the second argument to the class definition for output_bytes, so it simply skips trying to instantiate it. Not an error.

This means that the slightly wonky definition does exactly what I want it to do: It instantiates for all std::ostream classes, and doesn't for others.

Conclusion

Changing a template specialization so that it applies to an entire class hierarchy instead of just a single class can be done pretty easily using modern C++ tools. It involves some fairly benign changes to a class declaration, and imposes no runtime cost.

As always, this type of template metaprogramming is somewhat difficult to get your head around if you are a traditional C++ procedural programmer, as so many of us are. You have to get used to the concept of passing types as parameters at compile time, and this is just completely new territory.

As C++ adds more support for traits-based programming in the post-C++11 future, it may be that creating this type of class becomes simpler. But for now, it works pretty well without too much pain, as long as you can get your compiler updated to TR1 or later.

Note that boost provided libraries that implemented enable_if and is_base_of long before TR1, so you should be able to implement this type of code with just about any C++ compiler in use today. A home-grown implementation of enable_if is trivial, and is included in the sample code: The entire code for this example is available for download, including a Windows build file.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!