Template Partial Specialization In C++

Today I want to share something about the right way to emulate partial function template specialisation in C++. I learnt it by watching Arthur O’Dwyer’s CppCon talk Template Normal Programming.

Actually, the technique for emulating function template partial specialization through class template specialization is well known, but the naming convention used by Arthur is the clearest I’ve seen. And he has kindly accepted that I share it with you on Fluent C++. Jump to the bottom of the post to get to it directly.

All this is a great opportunity for a general review of template partial specialization in C++.

Partial Template Specialization

C++ allows to partially specialize class templates:

template<typename T> struct is_pointer : std::false_type {}; template<typename T> struct is_pointer<T*> : std::true_type {}; 1 2 3 4 5 template < typename T > struct is_pointer : std :: false_type { } ; template < typename T > struct is_pointer < T * > : std :: true_type { } ;

In the above code, is_pointer has a primary template (the first struct) and a specialization (the second one). And since the specialization still has a template parameter, it is called a partial specialization.

Partial specialization also works for variable templates:

template<typename T> bool is_pointer_v = false; template<typename T> bool is_pointer_v<T*> = true; 1 2 3 4 5 template < typename T > bool is_pointer_v = false ; template < typename T > bool is_pointer_v < T * > = true ;

But C++ forbids partial specialization on anything else than classes (or structs) and variables.

That means that alias template partial specialization is forbidden. So the following code is also invalid:

template<typename T> using myType = int; template<typename T> // compilation error! using myType<T*> = int*; 1 2 3 4 5 template < typename T > using myType = int ; template < typename T > // compilation error! using myType < T * > = int * ;

In fact, even total specialization of alias templates is forbidden.

And while function templates can be totally specialized, their partial specialization is illegal. So the following code:

template<typename T> constexpr bool is_pointer(T const&) { return false; } template<typename T> // compilation error! constexpr bool is_pointer<T*>(T const&) { return true; } 1 2 3 4 5 6 7 8 9 10 11 template < typename T > constexpr bool is_pointer ( T const & ) { return false ; } template < typename T > // compilation error! constexpr bool is_pointer < T * > ( T const & ) { return true ; }

leads to a compilation error.

Why can’t we partially specialize everything?

To be honest, I don’t know. But I wonder.

Herb Sutter touches upon the subject in More Exceptional C++ Item 10 and in Exceptional C++ Style Item 7, but it’s more about total specialization than partial. I suspect that the rationale for functions partial specialization is that it would allow mixing partial specializations with overloading, which would become too confusing.

Does anyone know the reason for restricting on function template specialization?

For aliases this answer on Stack Overflow gives some elements of information. In short, the using declaration is no more than an alias, that shouldn’t fetaure more logic. This explains why even total specialization are not allowed for aliases.

Emulating Partial Template Specialization

To emulate partial specialization on aliases and on functions, the technique is to fall back on the specialization that works on structs.

Here is how to go about it for alias templates:

template<typename T> struct MyTypeImpl { using type = int; }; template<typename T> struct MyTypeImpl<T*> { using type = int*; }; template<typename T> using myType = typename MyTypeImpl<T>::type; 1 2 3 4 5 6 7 8 template < typename T > struct MyTypeImpl { using type = int ; } ; template < typename T > struct MyTypeImpl < T * > { using type = int * ; } ; template < typename T > using myType = typename MyTypeImpl < T > :: type ;

As for functions templates, let me share the implementation that Arthur O’Dwyer’s CppCon demonstrates in his talk. He also uses a fall back on structs, but his naming conventions is the clearest that I’ve seen:

template<typename T> struct is_pointer_impl { static constexpr bool _() { return false; } }; template<typename T> struct is_pointer_impl<T*> { static constexpr bool _() { return true; } }; template<typename T> constexpr bool is_pointer(T const&) { return is_pointer_impl<T>::_(); } 1 2 3 4 5 6 7 8 9 10 11 template < typename T > struct is_pointer_impl { static constexpr bool _ ( ) { return false ; } } ; template < typename T > struct is_pointer_impl < T * > { static constexpr bool _ ( ) { return true ; } } ; template < typename T > constexpr bool is_pointer ( T const & ) { return is_pointer_impl < T > :: _ ( ) ; }

Did you notice the name of the static function inside the structs? It’s just an underscore, which is a legal name for a function in C++. Since these functions are just a technical artifact, I think it’s good to show that they carry no meaning by giving them (almost) no name.

With this you can emulate any missing template partial (or even total for aliases) specialization in C++.

Regardless of this, Template Normal Programming is a great talk, that shows you everything there is to know about templates, except in metaprogramming. It’s definitely worth watching.

Share this post! Don't want to miss out ?