Goethe–Schiller Monument, Syracuse, New York State (Image by Easchiff)
Providing Explicit Specialisations for Non‐Template Members of Class Template
C++ is full of surprises (albeit not always good ones ). It is a well-known fact that you can provide explicit specialisations for function templates and class templates. But it was a total surprise to me that you can define explicit specialisations for non‐template members of class template without specialising the class template itself!
The following non‐template members of class template can be explicitly specialised:
member function (either static or not);
static data member;
member enumeration (since C++11);
Let’s see some examples:
When this can be useful? Whenever you need some conditional logic for you class template based on template parameters, but that logic takes only a small fraction of class code. For example, you may have a class containing a dozen of methods, but only a couple of them require behaviour specific to template arguments. In that case, it is not practical to define explicit specialisation for the whole class due to large amount of code duplication. The better solution is to define specialisations only for those specific methods.
Perhaps the most useful part of this feature is the ability to specialise member functions. Let’s take a look at a concrete example.
Implementing Generic RAII Wrapper for Resource Handles
Opaque resource handles are used in many OS‐level, networking, and database APIs. Usually such handles must be closed with some kind of close_handle function. When dealing with handles in C++ you almost always want to use RAII1 wrapper to avoid handle leaks. We can define such a wrapper as follows:
Here OsHandle is some common handle type in our hypothetical operating system which needs to be closed by CloseOsHandle function (the real world example is Windows API HANDLE type with corresponding CloseHandle function). Example usage:
Now suppose we want to use OsInternetHandle which requires specific CloseOsInternetHandle function to be closed (this is also common in Windows API: for example, WinHttp HINTERNET handle requires WinHttpCloseHandle). We can specialise our Handle class for OsInternetHandle:
The problem is solved, but the cost is a lot of code duplication. As you can see, the only real difference between specialised and primary template is Cleanup method. Let’s leave our Handle class definition as is and define specialisation for Cleanup member function instead:
This is much better for maintainability and works exactly like the class specialisation above.
Note that our handle wrapper is somewhat simplified for the purpose of the article. It is assumed that various HandleType arguments are in fact different types, not an aliases for some built‐in type like int or void*. For example, if our OsHandle and OsInternetHandle are both defined as synonyms for int, the above specialisation will not work. For that reason, in real life you almost certainly should apply type‐safe handle idiom to your wrapper class described in the recent isocpp.org post by Emil Ernerfeldt. Then you can specialise Cleanup method on handle tag (which is effectively a form of tag dispatching2).
Although described in Section 14.7 [temp.spec] of the C++ ISO/IEC standard, this unobvious feature is poorly documented elsewhere. In fact, I’ve found only a couple of reference manuals mentioning it: