Chapter 16

Specialization and Overloading

So far we have studied how C++ templates allow a generic definition to be expanded into a family of related classes, functions, or variables. Although this is a powerful mechanism, there are many situations in which the generic form of an operation is far from optimal for a specific substitution of template parameters.

C++ is somewhat unique among other popular programming languages with support for generic programming because it has a rich set of features that enable the transparent replacement of a generic definition by a more specialized facility. In this chapter we study the two C++ language mechanisms that allow pragmatic deviations from pure generic-ness: template specialization and overloading of function templates.

16.1 When “Generic Code” Doesn’t Quite Cut It

Consider the following example:

template<typename T>
class Array {
  private:
    T* data;
    …
  public:
    Array(Array<T> const&);
    Array<T>& operator= (Array<T> const&);

  void exchangeWith (Array<T>* b) {
      T* tmp = data;
      data = b->data;
      b->data = tmp;
}
  T& operator[] (std::size_t k) {
      return data[k];
  }
  …
};

template<typename T> inline
void exchange (T* a, T* b)
{
     T tmp(*a);
     *a = *b;
     *b = tmp;
}

For simple types, the generic implementation of exchange() works well. However, for types with expensive copy operations, the generic implementation may be much more expensive—both in terms of machine cycles and in terms of memory usage—than an implementation that is tailored to the particular, given structure. In our example, the generic implementation requires one call to the copy constructor of Array<T> and two calls to its copy-assignment operator. For large data structures these copies can often involve copying relatively large amounts of memory. However, the functionality of exchange() could presumably often be replaced just by swapping the internal data pointers, as is done in the member function exchangeWith().

16.1.1 Transparent Customization

In our previous example, the member function exchangeWith() provides an efficient alternative to the generic exchange() function, but the need to use a different function is inconvenient in several ways:

1. Users of the Array class have to remember an extra interface and must be careful to use it when possible.

2. Generic algorithms can generally not discriminate between various possibilities. For example:

template<typename T>
void genericAlgorithm(T* x, T* y)
{
  …
  exchange(x, y);  // How do we select the right algorithm?
  …
}

Because of these considerations, C++ templates provide ways to customize function templates and class templates transparently. For function templates, this is achieved through the overloading mechanism. For example, we can write an overloaded set of quickExchange() function templates as follows:

Images

template<typename T>
void quickExchange(T* a, T* b)                // #1
{
    T tmp(*a);
    *a = *b;
    *b = tmp;
}

template<typename T>
void quickExchange(Array<T>* a, Array<T>* b) //  #2
{
    a->exchangeWith(b);
}

void demo(Array<int>* p1, Array<int>* p2)
{
    int x=42, y=-7;
    quickExchange(&x, &y);                  // uses #1
    quickExchange(p1, p2);                  // uses #2
}

The first call to quickExchange() has two arguments of type int*, and therefore deduction succeeds only with the first template, declared at point #1 , when T is substituted by int. There is therefore no doubt regarding which function should be called. In contrast, the second call can be matched with either template: Viable functions for the call quickExchange(p1, p2) are obtained both when substituting Array<int> for T in the first template and when substituting int in the second template. Furthermore, both substitutions result in functions with parameter types that exactly match the argument types of the second call. Ordinarily, this would lead us to conclude that the call is ambiguous, but (as we will discuss later) the C++ language considers the second template to be “more specialized” than the first. All other things being equal, overload resolution prefers the more specialized template and hence selects the template at point #2 .

16.1.2 Semantic Transparency

The use of overloading as shown in the previous section is very useful in achieving transparent customization of the instantiation process, but it is important to realize that this “transparency” depends a great deal on the details of the implementation. To illustrate this, consider our quickExchange() solution. Although both the generic algorithm and the one customized for Array<T> types end up swapping the values that are being pointed to, the side effects of the operations are very different. This is dramatically illustrated by considering some code that compares the exchange of struct objects with the exchange of Array<T>s:

Images

struct S {
    int x;
} s1, s2;

void distinguish (Array<int> a1, Array<int> a2)
{
    int* p = &a1[0];
    int* q = &s1.x;
    a1[0] = s1.x = 1;
    a2[0] = s2.x = 2;
    quickExchange(&a1, &a2);  // *p == 1 after this (still)
    quickExchange(&s1, &s2);  // *q == 2 after this
}

This example shows that a pointer p into the first Array becomes a pointer into the second array after quickExchange() is called. However, the pointer into the non-Array s1 remains pointing into s1 even after the exchange operation: Only the values that were pointed to were exchanged. The difference is significant enough that it may confuse clients of the template implementation. The prefix quick_ is helpful in attracting attention to the fact that a shortcut may be taken to realize the desired operation. However, the original generic exchange() template can still have a useful optimization for Array<T>s:

template<typename T>
void exchange (Array<T>* a, Array<T>* b)
{
    T* p = &(*a)[0];
    T* q = &(*b)[0];
    for (std::size_t k = a->size(); k-- != 0; ) {
        exchange(p++, q++);
    }
}

The advantage of this version over the generic code is that no (potentially) large temporary Array<T> is needed. The exchange() template is called recursively so that good performance is achieved even for types such as Array<Array<char>>. Note also that the more specialized version of the template is not declared inline because it does a considerable amount of work of its own, whereas the original generic implementation is inline because it performs only a few operations (each of which is potentially expensive).

16.2 Overloading Function Templates

In the previous section we saw that two function templates with the same name can coexist, even though they may be instantiated so that both have identical parameter types. Here is another simple example of this:

Images

details/funcoverload1.hpp

template<typename T>
int f(T)
{
    return 1;
}

template<typename T>
int f(T*)
{
    return 2;
}

When T is substituted by int* in the first template, a function is obtained that has exactly the same parameter (and return) types as the one obtained by substituting int for T in the second template. Not only can these templates coexist, their respective instantiations can coexist even if they have identical parameter and return types.

The following demonstrates how two such generated functions can be called using explicit template argument syntax (assuming the previous template declarations):

details/funcoverload1.cpp

#include <iostream>
#include "funcoverload1.hpp"

int main()
{
    std::cout << f<int*>((int*)nullptr);  // calls f<T>(T)
    std::cout << f<int>((int*)nullptr);   // calls f<T>(T*)
}

This program has the following output:

12

To clarify this, let’s analyze the call f<int*>((int*)nullptr) in detail. The syntax f<int*>() indicates that we want to substitute the first template parameter of the template f() with int* without relying on template argument deduction. In this case there is more than one template f(), and therefore an overload set is created containing two functions generated from templates: f<int*>(int*) (generated from the first template) and f<int*>(int**) (generated from the second template). The argument to the call (int*)nullptr has type int*. This matches only the function generated from the first template, and hence that is the function that ends up being called.

For the second call, on the other hand, the created overloading set contains f<int>(int) (generated from the first template) and f<int>(int*) (generated from the second template), so that only the second template matches.

16.2.1 Signatures

Two functions can coexist in a program if they have distinct signatures. We define the signature of a function as the following information:1. The unqualified name of the function (or the name of the function template from which it was generated)

2. The class or namespace scope of that name and, if the name has internal linkage, the translation unit in which the name is declared

3. The const, volatile, or const volatile qualification of the function (if it is a member function with such a qualifier)

4. The & or && qualification of the function (if it is a member function with such a qualifier)

5. The types of the function parameters (before template parameters are substituted if the function is generated from a function template)

6. Its return type, if the function is generated from a function template

7. The template parameters and the template arguments, if the function is generated from a function template

This means that the following templates and their instantiations could, in principle, coexist in the same program:

template<typename T1, typename T2>
void f1(T1, T2);

template<typename T1, typename T2>
void f1(T2, T1);

template<typename T>
long f2(T);

template<typename T>
char f2(T);

However, they cannot always be used when they’re declared in the same scope because instantiating both creates an overload ambiguity. For example, calling f2(42) when both the templates above are declared will clearly create an ambiguity. Another example is illustrated below:

Images

#include <iostream>

template<typename T1, typename T2>
void f1(T1, T2)
{
    std::cout << "f1(T1, T2)\n";
}
template<typename T1, typename T2>
void f1(T2, T1)
{
    std::cout << "f1(T2, T1)\n";
}
// fine so far

int main()
{
    f1<char, char>(’a’, ’b’);  // ERROR: ambiguous
}

Here, the function

f1<T1 = char, T2 = char>(T1, T2)

can coexist with the function

f1<T1 = char, T2 = char>(T2, T1)

but overload resolution will never prefer one over the other. If the templates appear in different translation units, then the two instantiations can actually exist in the same program (and, e.g., a linker should not complain about duplicate definitions because the signatures of the instantiations are distinct):

// translation unit 1:
#include <iostream>

template<typename T1, typename T2>
void f1(T1, T2)
{
    std::cout << "f1(T1, T2)\n";
}

void g()
{
    f1<char, char>(’a’, ’b’);
}

// translation unit 2:
#include <iostream>

template<typename T1, typename T2>
void f1(T2, T1)
{
    std::cout << "f1(T2, T1)\n";
}
extern void g();  // defined in translation unit 1

int main()
{
    f1<char, char>(’a’, ’b’);
    g();
}

This program is valid and produces the following output:

f1(T2, T1)

f1(T1, T2)

16.2.2 Partial Ordering of Overloaded Function Templates

Reconsider our earlier example: We found that after substituting the given template argument lists (<int*> and <int>), overload resolution ended up selecting the right function to call:

std::cout << f<int*>((int*)nullptr);  // calls f<T>(T)
std::cout << f<int>((int*)nullptr);   // calls f<T>(T*)

However, a function is selected even when explicit template arguments are not provided. In this case, template argument deduction comes into play. Let’s slightly modify function main() in the previous example to discuss this mechanism:

details/funcoverload2.cpp

#include <iostream>

template<typename T>
int f(T)
{
    return 1;
}

template<typename T>
int f(T*)
{
    return 2;
}

int main()
{
    std::cout << f(0);              // calls f<T>(T)
    std::cout << f(nullptr);        // calls f<T>(T)
    std::cout << f((int*)nullptr);  // calls f<T>(T*)
}

Consider the first call, f(0): The type of the argument is int, which matches the type of the parameter of the first template if we substitute T with int. However, the parameter type of the second template is always a pointer and, hence, after deduction, only an instance generated from the first template is a candidate for the call. In this case overload resolution is trivial.

The same applies to the second call: (f(nullptr): The type of the argument is std::nullptr_t, which again only matches for the first template.

The third call (f((int*)nullptr)) is more interesting: Argument deduction succeeds for both templates, yielding the functions f<int*>(int*) and f<int>(int*). From a traditional overload resolution perspective, both are equally good functions to call with an int* argument, which would suggest that the call is ambiguous (see Appendix C). However, in this sort of case, an additional overload resolution criterion comes into play: The function generated from the more specialized template is selected. Here (as we see shortly), the second template is considered more specialized and thus the output of our example is

112

16.2.3 Formal Ordering Rules

In our last example, it may seem very intuitive that the second template is more special than the first because the first can accommodate just about any argument type, whereas the second allows only pointer types. However, other examples are not necessarily as intuitive. In what follows, we describe the exact procedure to determine whether one function template participating in an overload set is more specialized than the other. Note that these are partial ordering rules: It is possible that given two templates, neither can be considered more specialized than the other. If overload resolution must select between two such templates, no decision can be made, and the program contains an ambiguity error.

Let’s assume we are comparing two identically named function templates that seem viable for a given function call. Overload resolution is decided as follows:

• Function call parameters that are covered by a default argument and ellipsis parameters that are not used are ignored in what follows.

• We then synthesize two artificial lists of argument types (or for conversion function templates, a return type) by substituting every template parameter as follows:

1. Replace each template type parameter with a unique invented type.

2. Replace each template template parameter with a unique invented class template.

3. Replace each nontype template parameter with a unique invented value of the appropriate type. (Types, templates, and values that are invented in this context are distinct from any other types, templates, or values that either the programmer used or the compiler synthesized in other contexts.)

• If template argument deduction of the second template against the first synthesized list of argument types succeeds with an exact match, but not vice versa, then the first template is more specialized than the second. Conversely, if template argument deduction of the first template against the second synthesized list of argument types succeeds with an exact match, but not vice versa, then the second template is more specialized than the first. Otherwise (either no deduction succeeds or both succeed), there is no ordering between the two templates. Let’s make this concrete by applying it to the two templates in our last example. From these two templates, we synthesize two lists of argument types by replacing the template parameters as described earlier: (A1) and (A2*) (where A1 and A2 are unique made up types). Clearly, deduction of the first template against the second list of argument types succeeds by substituting A2* for T. However, there is no way to make T* of the second template match the nonpointer type A1 in the first list. Hence, we formally conclude that the second template is more specialized than the first.

Consider a more intricate example involving multiple function parameters:

template<typename T>
void t(T*, T const* = nullptr, …);

template<typename T>
void t(T const*, T*, T* = nullptr);

void example(int* p)
{
    t(p, p);
}

First, because the actual call does not use the ellipsis parameter for the first template and the last parameter of the second template is covered by its default argument, these parameters are ignored in the partial ordering. Note that the default argument of the first template is not used; hence the corresponding parameter participates in the ordering.

The synthesized lists of argument types are (A1*, A1 const*) and (A2 const*, A2*). Template argument deduction of (A1*, A1 const*) versus the second template actually succeeds with the substitution of T with A1 const, but the resulting match is not exact because a qualification adjustment is needed to call t<A1 const>(A1 const*, A1 const*, A1 const* = 0) with arguments of types (A1*, A1 const*). Similarly, no exact match can be found by deducing template arguments for the first template from the argument type list (A2 const*, A2*). Therefore, there is no ordering relationship between the two templates, and the call is ambiguous.

The formal ordering rules generally result in the intuitive selection of function templates. Once in a while, however, an example comes up for which the rules do not select the intuitive choice. It is therefore possible that the rules will be revised to accommodate those examples in the future.

16.2.4 Templates and Nontemplates

Function templates can be overloaded with nontemplate functions. All else being equal, the non-template function is preferred in selecting the actual function being called. The following example illustrates this:

Images

details/nontmpl1.cpp

#include <string>
#include <iostream>

template<typename T>
std::string f(T)
{
    return "Template";
}

std::string f(int&)
{
    return "Nontemplate";
}

int main()
{
    int x = 7;
    std::cout << f(x) << ’\n’;//prints: Nontemplate
}

This outputs

Nontemplate

However, when const and reference qualifiers differ, priorities for overload resolution can change. For example:

details/nontmpl2.cpp

#include <string>
#include <iostream>

template<typename T>
std::string f(T&)
{
    return "Template";
}

std::string f(int const&)
{
    return "Nontemplate";
}

int main()
{
    int x = 7;
    std::cout << f(x) << ’\n’;//prints: Template
    int const c = 7;
    std::cout << f(c) << ’\n’;//prints: Nontemplate
}

The program has the following output:

Template

Nontemplate

Now, the function template f<>(T&) is a better match when passing a nonconstant int. The reason is that for an int the instantiated f<>(int&) is a better match than f(int const&). Thus, the difference is not only the fact that one function is a template and the other is not. In that case the general rules of overload resolution apply (see Section C.2 on page 682). Only when calling f() for a int const, do both signatures have the same type int const&, so that the nontemplate is preferred.

For this reason, it’s a good idea to declare the member function template as

template<typename T>
std::string f(T const&)
{
    return "Template";
}

Nevertheless, this effect can easily occur accidentally and cause surprising behavior when member functions are defined that accept the same arguments as copy or move constructors. For example:

details/tmplconstr.cpp

#include <string>
#include <iostream>

class C {
  public:
    C() = default;
    C (C const&) {
      std::cout << "copy constructor\n";
    }
    C (C&&) {
      std::cout << "move constructor\n";
    }
    template<typename T>
    C (T&&) {
      std::cout << "template constructor\n";
    }
};

int main()
{
    C x;
    C x2{x};             // prints: template constructor
    C x3{std::move(x)};  // prints: move constructor
    C const c;
    C x4{c};             // prints: copy constructor
    C x5{std::move(c)};  // prints: template constructor
}

The program has the following output:

template constructor
move constructor
copy constructor
template constructor

Thus, the member function template is a better match for copying a C than the copy constructor. And for std::move(c), which yields type C const&& (a type that is possible but usually doesn’t have meaningful semantics), the member function template also is a better match than the move constructor.

For this reason, usually you have to partially disable such member function templates when they might hide copy or move constructors. This is explained in Section 6.4 on page 99.

16.2.5 Variadic Function Templates

Variadic function templates (see Section 12.4 on page 200) require some special treatment during partial ordering, because deduction for a parameter pack (described in Section 15.5 on page 275) matches a single parameter to multiple arguments. This behavior introduces several interesting situations for function template ordering, illustrated by the following example:

details/variadicoverload.cpp

#include <iostream>

template<typename T>
int f(T*)
{
  return 1;
}

template<typename… Ts>
int f(Ts…)
{
  return 2;
}

template<typename… Ts>
int f(Ts*…)
{
  return 3;
}
int main()
{
  std::cout << f(0, 0.0);                          // calls f<>(Ts…)
  std::cout << f((int*)nullptr, (double*)nullptr); // calls f<>(Ts*…)
  std::cout << f((int*)nullptr);                   // calls f<>(T*)
}

The output of this example, which we will discuss in a moment, is

231

In the first call, f(0, 0.0), each of the function templates named f is considered. For the first function template, f(T*), deduction fails both because the template parameter T cannot be deduced and because there are more function arguments than parameters for this nonvariadic function template. The second function template, f(Ts…), is variadic: Deduction in this case compares the pattern of a function parameter pack (Ts) against against the types of the two arguments (int and double, respectively), deducing Ts to the sequence (int, double). For the third function template, f(Ts*…), deduction compares the pattern of the function parameter pack Ts* against each of the argument types. This deduction fails (Ts cannot be deduced), leaving only the second function template viable. Function template ordering is not required.

The second call, f((int*)nullptr, (double*)nullptr), is more interesting: Deduction fails for the first function template because there are more function arguments than parameters, but deduction succeeds for the second and third templates. Written explicitly, the resulting calls would be

f<int*,double*>((int*)nullptr, (double*)nullptr)        //for second template

and

f<int,double>((int*)nullptr, (double*)nullptr)        //for third template

Partial ordering then considers the second and third templates, both of which are variadic as follows: When applying the formal ordering rules described in Section 16.2.3 on page 331 to a variadic template, each template parameter pack is replaced by a single made-up type, class template, or value. For example, this means that the synthesized argument types for the second and third function templates are A1 and A2*, respectively, where A1 and A2 are unique, made-up types. Deduction of the second template against the third’s list of argument types succeeds by substituting the single-element sequence (A2*) for the parameter pack Ts. However, there is no way to make the pattern Ts* of the third template’s parameter pack match the nonpointer type A1, so the third function template (which accepts pointer arguments) is considered more specialized than the second function template (which accepts any arguments).

The third call, f((int*)nullptr), introduces a new wrinkle: Deduction succeeds for all three of the function templates, requiring partial ordering to compare a nonvariadic template to a variadic template. To illustrate, we compare the first and third function templates. Here, the synthesized argument types are A1* and A2*, where A1 and A2 are unique, made-up types. Deduction of the first template against the third’s synthesized argument list would normally succeed by substituting A2 for T. In the other direction, deduction of the third template against the first’s synthesized argument list succeeds by substituting the single-element sequence (A1) for the parameter pack Ts. Partial ordering between the first and third templates would normally result in an ambiguity. However, a special rule prohibits an argument that originally came from a function parameter pack (e.g., the third template’s parameter pack Ts*…) from matching a parameter that is not a parameter pack (the first template’s parameter T *). Hence, template deduction of the first template against the third’s synthesized argument list fails, and the first template is considered more specialized than the third. This special rule effectively considers nonvariadic templates (those with a fixed number of parameters) to be more specialized than variadic templates (with a variable number of parameters).

The rules described above apply equally to pack expansions that occur in types in the function signature. For example, we can wrap the parameters and arguments of each of the function templates in our previous example into a variadic class template Tuple to arrive at a similar example not involving function parameter packs:

details/tupleoverload.cpp

#include <iostream>

template<typename… Ts> class Tuple
{
};

template<typename T>
int f(Tuple<T*>)
{
  return 1;
}

template<typename… Ts>
int f(Tuple<Ts…>)
{
  return 2;
}

template<typename… Ts>
int f(Tuple<Ts*…>)
{
  return 3;
}

int main()
{
  std::cout << f(Tuple<int, double>());    // calls f<>(Tuple<Ts…>)
  std::cout << f(Tuple<int*, double*>());  // calls f<>(Tuple<Ts*…>)
  std::cout << f(Tuple<int*>());           // calls f<>(Tuple<T*>)
}

Function template ordering considers the pack expansions in the template arguments to Tuple analogously to the function parameter packs in our previous example, resulting in the same output:

231

16.3 Explicit Specialization

The ability to overload function templates, combined with the partial ordering rules to select the “best” matching function template, allows us to add more specialized templates to a generic implementation to tune code transparently for greater efficiency. However, class templates and variable templates cannot be overloaded. Instead, another mechanism was chosen to enable transparent customization of class templates: explicit specialization. The standard term explicit specialization refers to a language feature that we call full specialization instead. It provides an implementation for a template with template parameters that are fully substituted: No template parameters remain. Class templates, function templates, and variable templates can be fully specialized.2

So can members of class templates that may be defined outside the body of a class definition (i.e., member functions, nested classes, static data members, and member enumeration types).

In a later section, we will describe partial specialization. This is similar to full specialization, but instead of fully substituting the template parameters, some parameterization is left in the alternative implementation of a template. Full specializations and partial specializations are both equally “explicit” in our source code, which is why we avoid the term explicit specialization in our discussion. Neither full nor partial specialization introduces a totally new template or template instance. Instead, these constructs provide alternative definitions for instances that are already implicitly declared in the generic (or unspecialized) template. This is a relatively important conceptual observation, and it is a key difference with overloaded templates.

16.3.1 Full Class Template Specialization

A full specialization is introduced with a sequence of three tokens: template, <, and >.3 In addition, the class name is followed by the template arguments for which the specialization is declared. The following example illustrates this:

template<typename T>
class S {
  public:
    void info() {
        std::cout << "generic (S<T>::info())\n";
    }
};

template<>
class S<void> {
  public:
    void msg() {
        std::cout << "fully specialized (S<void>::msg())\n";
    }
};

Note how the implementation of the full specialization does not need to be related in any way to the generic definition: This allows us to have member functions of different names (info versus msg). The connection is solely determined by the name of the class template.

The list of specified template arguments must correspond to the list of template parameters. For example, it is not valid to specify a nontype value for a template type parameter. However, template arguments for parameters with default template arguments are optional:

template<typename T> class Types {
  public:
    using I = int;
};

template<typename T, typename U = typename Types<T>::I>
class S;                        //#1

template<>
class S<void> {                 // #2
  public:
    void f();
};

template<> class S<char, char>; // #3

template<> class S<char, 0>;    // ERROR: 0 cannot substitute U

int main()
{
    S<int>*      pi;    // OK: uses #1 , no definition needed
    S<int>       e1;    // ERROR: uses #1 , but no definition available
    S<void>*     pv;    // OK: uses #2
    S<void,int>  sv;    // OK: uses #2 , definition available
    S<void,char> e2;    // ERROR: uses #1 , but no definition available
    S<char,char> e3;    // ERROR: uses #3 , but no definition available
}

template<>
class S<char, char> {  //definition for #3
};

As this example also shows, declarations of full specializations (and of templates) do not necessarily have to be definitions. However, when a full specialization is declared, the generic definition is never used for the given set of template arguments. Hence, if a definition is needed but none is provided, the program is in error. For class template specialization, it is sometimes useful to “forward declare” types so that mutually dependent types can be constructed. A full specialization declaration is identical to a normal class declaration in this way (it is not a template declaration). The only differences are the syntax and the fact that the declaration must match a previous template declaration. Because it is not a template declaration, the members of a full class template specialization can be defined using the ordinary out-of-class member definition syntax (in other words, the template<> prefix cannot be specified):

template<typename T>
class S;

template<> class S<char**> {
  public:
    void print() const;
};

// the following definition cannot be preceded by template<>
void S<char**>::print() const
{
    std::cout << "pointer to pointer to char\n";
}

A more complex example may reinforce this notion:

template<typename T>
class Outside {
  public:     template<typename U>
    class Inside {
    }; };

template<>
class Outside<void> {
    // there is no special connection between the following nested class
    // and the one defined in the generic template
    template<typename U>
    class Inside {
      private:
        static int count;
    };
};
// the following definition cannot be preceded by template<>
template<typename U>
int Outside<void>::Inside<U>::count = 1;

A full specialization is a replacement for the instantiation of a certain generic template, and it is not valid to have both the explicit and the generated versions of a template present in the same program. An attempt to use both in the same file is usually caught by a compiler:

template<typename T>
class Invalid {
};

Invalid<double> x1;     // causes the instantiation of Invalid<double>

template<>
class Invalid<double>;  // ERROR: Invalid<double> already instantiated

Unfortunately, if the uses occur in different translation units, the problem may not be caught so easily. The following invalid C++ example consists of two files and compiles and links on many implementations, but it is invalid and dangerous:

// Translation unit 1:
template<typename T>
class Danger {
  public:
    enum { max = 10 };
};
char buffer[Danger<void> ::max];  // uses generic value

extern void clear(char*);

int main()
{
    clear(buffer);
}

// Translation unit 2:
template<typename T>
class Danger;

template<>
class Danger<void> {
  public:
    enum { max = 100 };
};

void clear(char* buf)
{
    // mismatch in array bound:
    for (int k = 0; k<Danger<void> ::max; ++k) {
      buf[k] = ’\0’;
    }
}

This example is clearly contrived to keep it short, but it illustrates that care must be taken to ensure that the declaration of the specialization is visible to all the users of the generic template. In practical terms, this means that a declaration of the specialization should normally follow the declaration of the template in its header file. When the generic implementation comes from an external source (such that the corresponding header files should not be modified), this is not necessarily practical, but it may be worth creating a header including the generic template followed by declarations of the specializations to avoid these hard-to-find errors. We find that, in general, it is better to avoid specializing templates coming from an external source unless it is clearly marked as being designed for that purpose.

16.3.2 Full Function Template Specialization

The syntax and principles behind (explicit) full function template specialization are much the same as those for full class template specialization, but overloading and argument deduction come into play.

The full specialization declaration can omit explicit template arguments when the template being specialized can be determined via argument deduction (using as argument types the parameter types provided in the declaration) and partial ordering. For example:

template<typename T>
int f(T)                    // #1
{
    return 1;
}

template<typename T>
int f(T*)                   // #2
{
    return 2;
}

template<> int f(int)      // OK: specialization of #1
{
    return 3;
}
template<> int f(int*)    // OK: specialization of #2
{
    return 4;
}

A full function template specialization cannot include default argument values. However, any default arguments that were specified for the template being specialized remain applicable to the explicit specialization:

template<typename T>
int f(T, T x = 42)
{
    return x;
}

template<> int f(int, int = 35)  // ERROR
{
    return 0;
}

(That’s because a full specialization provides an alternative definition, but not an alternative declaration. At the point of a call to a function template, the call is entirely resolved based on the function template.)

A full specialization is in many ways similar to a normal declaration (or rather, a normal redeclaration). In particular, it does not declare a template, and therefore only one definition of a noninline full function template specialization should appear in a program. However, we must still ensure that a declaration of the full specialization follows the template to prevent attempts at using the function generated from the template. The declarations for a template g() and one full specialization would therefore typically be organized in two files as follows:

• The interface file contains the definitions of primary templates and partial specializations but declares only the full specializations:

#ifndef TEMPLATE_G_HPP
#define TEMPLATE_G_HPP

// template definition should appear in header file:
template<typename T>
int g(T, T x = 42)
{
    return x;
}

// specialization declaration inhibits instantiations of the template;
// definition should not appear here to avoid multiple definition errors
template<> int g(int, int y);

#endif // TEMPLATE_G_HPP

• The corresponding implementation file defines the full specialization:

#include "template_g.hpp"

template<> int g(int, int y)
{
    return y/2;
}

Alternatively, the specialization could be made inline, in which case its definition can be (and should be) placed in the header file.

16.3.3 Full Variable Template Specialization

Variable templates can also be fully specialized. By now, the syntax should be intuitive:

template<typename T> constexpr std::size_t SZ = sizeof(T);
template<> constexpr std::size_t SZ<void> = 0;

Clearly, the specialization can provide an initializer that is distinct from that resulting from the template. Interestingly, a variable template specialization is not required to have a type matching that of the template being specialized:

template<typename T> typename T::iterator null_iterator;
template<> BitIterator null_iterator<std::bitset<100>>;
                  // BitIterator doesn’t match T::iterator, and that is fine

16.3.4 Full Member Specialization

Not only member templates, but also ordinary static data members and member functions of class templates, can be fully specialized. The syntax requires template<> prefix for every enclosing class template. If a member template is being specialized, a template<> must also be added to denote that it is being specialized. To illustrate the implications of this, let’s assume the following declarations:

template<typename T>
class Outer {                        // #1
  public:
    template<typename U>
    class Inner {                    // #2
      private:
        static int count;            // #3
    };
    static int code;                 // #4
    void print() const {             // #5
      std::cout << "generic";
    }
};
template<typename T>
int Outer<T>::code = 6;              // #6

template<typename T> template<typename U>
int Outer<T>::Inner<U>::count = 7;   // #7

template<>
class Outer<bool> {                  // #8
  public:
    template<typename U>
    class Inner {                    // #9
      private:
        static int count;            //#10
    };
    void print() const {             //#11
    }
};

The ordinary members code at point #4 and print() at point #5 of the generic Outer template #1 have a single enclosing class template and hence need one template<> prefix to specialize them fully for a specific set of template arguments:

    template<>
    int Outer<void>::code = 12;

    template<>
    void Outer<void>::print() const
    {
        std::cout << "Outer<void>";
    }

These definitions are used over the generic ones at points #4 and #5 for class Outer<void>, but other members of class Outer<void> are still generated from the template at point #1 . Note that after these declarations, it is no longer valid to provide an explicit specialization for Outer<void>.

Just as with full function template specializations, we need a way to declare the specialization of an ordinary member of a class template without specifying a definition (to prevent multiple definitions). Although nondefining out-of-class declarations are not allowed in C++ for member functions and static data members of ordinary classes, they are fine when specializing members of class templates. The previous definitions could be declared with

template<>
int Outer<void>::code;

template<>
void Outer<void>::print() const;

The attentive reader might point out that the nondefining declaration of the full specialization of Outer<void>::code looks exactly like a definition to be initialized with a default constructor. This is indeed so, but such declarations are always interpreted as nondefining declarations. For a full specialization of a static data member with a type that can only be initialized using a default constructor, we must resort to initializer list syntax. Given the following:

class DefaultInitOnly {
  public:
    DefaultInitOnly() = default;
    DefaultInitOnly(DefaultInitOnly const&) = delete;
};


template<typename T>
class Statics {
  private:
    static T sm;
};

the following is a declaration:

template<>
DefaultInitOnly Statics<DefaultInitOnly>::sm;

while the following is a definition that calls the default constructor:

template<>
DefaultInitOnly Statics<DefaultInitOnly>::sm{};

Prior to C++11, this was not possible. Default initialization was thus not available for such specializations. Typically, an initializer copying a default value was used:

template<>
DefaultInitOnly Statics<DefaultInitOnly>::sm = DefaultInitOnly();

Unfortunately, for our example that was not possible because the copy constructor is deleted. However, C++17 introduced mandatory copy-elision rules, which make that alternative valid, because no copy constructor invocation is involved anymore.

The member template Outer<T>::Inner can also be specialized for a given template argument without affecting the other members of the specific instantiation of Outer<T>, for which we are specializing the member template. Again, because there is one enclosing template, we will need one template<> prefix. This results in code like the following:

template<>
    template<typename X>
  class Outer<wchar_t>::Inner {
      public:
        static long count; // member type changed
    };

template<>
   template<typename X>
   long Outer<wchar_t>::Inner<X>::count;

The template Outer<T>::Inner can also be fully specialized, but only for a given instance of Outer<T>. We now need two template<> prefixes: one because of the enclosing class and one because we’re fully specializing the (inner) template:

template<>
    template<>
    class Outer<char>::Inner<wchar_t> {
      public:
        enum { count = 1 };
    };

// the following is not valid C++:
// template<> cannot follow a template parameter list
template<typename X>
template<> class Outer<X>::Inner<void>;  // ERROR

Contrast this with the specialization of the member template of Outer<bool>. Because the latter is already fully specialized, there is no enclosing template, and we need only one template<> prefix:

template<>
class Outer<bool>::Inner<wchar_t> {
  public:
    enum { count = 2 };
};

16.4 Partial Class Template Specialization

Full template specialization is often useful, but sometimes it is natural to want to specialize a class template or variable template for a family of template arguments rather than just one specific set of template arguments. For example, let’s assume we have a class template implementing a linked list:

template<typename T>
class List {          // #1
  public:
   …
   void append(T const&);
   inline std::size_t length() const;
   …
};

A large project making use of this template may instantiate its members for many types. For member functions that are not expanded inline (say, List<T>::append()), this may cause noticeable growth in the object code. However, we may know that from a low-level point of view, the code for List<int*>::append() and List<void*>::append() is the same. In other words, we’d like to specify that all Lists of pointers share an implementation. Although this cannot be expressed in C++, we can achieve something quite close by specifying that all Lists of pointers should be instantiated from a different template definition:

template<typename T>
class List<T*> {     // #2
  private:
    List<void*> impl;
    …
  public:
    …
   inline void append(T* p) {
      impl.append(p);
   }
   inline std::size_t length() const {
       return impl.length();
   }
   …
};

In this context, the original template at point #1 is called the primary template, and the latter definition is called a partial specialization (because the template arguments for which this template definition must be used have been only partially specified). The syntax that characterizes a partial specialization is the combination of a template parameter list declaration (template<>) and a set of explicitly specified template arguments on the name of the class template (<T*> in our example).

Our code contains a problem because List<void*> recursively contains a member of that same List<void*> type. To break the cycle, we can precede the previous partial specialization with a full specialization:

template<>
class List<void*> {    // #3
    …
    void append (void* p);
    inline std::size_t length() const;
    …
};

This works because matching full specializations are preferred over partial specializations. As a result, all member functions of Lists of pointers are forwarded (through easily inlineable functions) to the implementation of List<void*>. This is an effective way to combat code bloat (of which C++ templates are often accused).

There exist several limitations on the parameter and argument lists of partial specialization declarations. Some of them are as follows:

1. The arguments of the partial specialization must match in kind (type, nontype, or template) the corresponding parameters of the primary template.

2. The parameter list of the partial specialization cannot have default arguments; the default arguments of the primary class template are used instead.

3. The nontype arguments of the partial specialization should be either nondependent values or plain nontype template parameters. They cannot be more complex dependent expressions like 2*N (where N is a template parameter).

4. The list of template arguments of the partial specialization should not be identical (ignoring renaming) to the list of parameters of the primary template.

5. If one of the template arguments is a pack expansion, it must come at the end of a template argument list.

An example illustrates these limitations:

template<typename T, int I = 3>
class S;                  // primary template

template<typename T>
class S<int, T>;          // ERROR: parameter kind mismatch

template<typename T = int>
class S<T, 10>;          // ERROR: no default arguments

template<int I>
class S<int, I*2>;       // ERROR: no nontype expressions

template<typename U, int K>
class S<U, K>;           // ERROR: no significant difference from primary template

template<typename… Ts>
class Tuple;

template<typename Tail, typename… Ts>
class Tuple<Ts…, Tail>;  // ERROR: pack expansion not at the end

template<typename Tail, typename… Ts>
class Tuple<Tuple<Ts…>, Tail>;  // OK: pack expansion is at the end of a
                               // nested template argument list

Every partial specialization—like every full specialization—is associated with the primary template. When a template is used, the primary template is always the one that is looked up, but then the arguments are also matched against those of the associated specializations (using template argument deduction, as described in Chapter 15) to determine which template implementation is picked. Just as with function template argument deduction, the SFINAE principle applies here: If, while attempting to match a partial specialization an invalid construct is formed, that specialization is silently abandoned and another candidate is examined if one is available. If no matching specializations is found, the primary template is selected. If multiple matching specializations are found, the most specialized one (in the sense defined for overloaded function templates) is selected; if none can be called most specialized, the program contains an ambiguity error.

Finally, we should point out that it is entirely possible for a class template partial specialization to have more or fewer parameters than the primary template. Consider our generic template List, declared at point #1 , again. We have already discussed how to optimize the list-of-pointers case, but we may want to do the same with certain pointer-to-member types. The following code achieves this for pointer-to-member-pointers:

// partial specialization for any pointer-to-void* member
template<typename C>
class List<void* C::*> {
// #4
  public:
    using ElementType = void* C::*;
    …
    void append(ElementType pm);
    inline std::size_t length() const;
    …
};

// partial specialization for any pointer-to-member-pointer type except
// pointer-to-void* member, which is handled earlier
// (note that this partial specialization has two template parameters,
// whereas the primary template only has one parameter)
// this specialization makes use of the prior one to achieve the
// desired optimization
template<typename T, typename C>
class List<T* C::*> {    // #5
  private:
    List<void* C::*> impl;
    …
  public:
    using ElementType = T* C::*;
    …
    inline void append(ElementType pm) {
        impl.append((void* C::*)pm);
    }
    inline std::size_t length() const {
        return impl.length();
    }
    …
};

In addition to our observation regarding the number of template parameters, note that the common implementation defined at #4 to which all others are forwarded by the declaration at point #5 is itself a partial specialization (for the simple pointer case it is a full specialization). However, it is clear that the specialization at point #4 is more specialized than that at point #5 ; thus no ambiguity should occur.

Moreover, it is even possible that the number of explicitly written template arguments can differ from the number of template parameters in the primary template. This can happen both with default template arguments and, in a far more useful manner, with variadic templates:

template<typename… Elements>
class Tuple; // primary template

template<typename T1>
class Tuple<T>; // one-element tuple

template<typename T1, typename T2, typename… Rest>
class Tuple<T1, T2, Rest…>; // tuple with two or more elements

16.5 Partial Variable Template Specialization

When variable templates were added to the draft C++11 standard, several aspects of their specifications were overlooked, and some of those issues have still not been formally resolved. However, actual implementations generally agree on the handling of these issues.

Perhaps the most surprising of these issues is that the standard refers to the ability to partially specialize variable templates, but it does not describe how they are declared or what they mean. What follows is thus based on C++ implementations in practice (which do permit such partial specializations), and not on the C++ standard.

As one would expect, the syntax is similar to full variable template specialization, except that template<> is replaced by an actual template declaration header, and the template argument list following the variable template name must depend on template parameters. For example:

template<typename T> constexpr std::size_t SZ = sizeof(T);

template<typename T> constexpr std::size_t SZ<T&> = sizeof(void*);

As with the full specialization of variable templates, the type of a partial specialization is not required to match that of the primary template:

template<typename T> typename T::iterator null_iterator;

template<typename T, std::size_t N> T* null_iterator<T[N]> = null_ptr;
                // T* doesn’t match T::iterator, and that is fine

The rules regarding the kinds of template arguments that can be specified for a variable template partial specialization are identical to those for class template specializations. Similarly, the rules to select a specialization for a given list of concrete template arguments are identical too.

16.6 Afternotes

Full template specialization was part of the C++ template mechanism from the start. Function template overloading and class template partial specialization, on the other hand, came much later. The HP aC++ compiler was the first to implement function template overloading, and EDG’s C++ front end was the first to implement class template partial specialization. The partial ordering principles described in this chapter were originally invented by Steve Adamczyk and John Spicer (who are both of EDG).

The ability of template specializations to terminate an otherwise infinitely recursive template definition (such as the List<T*> example presented in Section 16.4 on page 348) was known for a long time. However, Erwin Unruh was perhaps the first to note that this could lead to the interesting notion of template metaprogramming: using the template instantiation mechanism to perform nontrivial computations at compile time. We devote Chapter 23 to this topic.

You may legitimately wonder why only class templates and variable templates can be partially specialized. The reasons are mostly historical. It is probably possible to define the same mechanism for function templates (see Chapter 17). In some ways, the effect of overloading function templates is similar, but there are also some subtle differences. These differences are mostly related to the fact that only the primary template needs to be looked up when a use is encountered. The specializations are considered only afterward, to determine which implementation should be used. In contrast, all overloaded function templates must be brought into an overload set by looking them up, and they may come from different namespaces or classes. This increases the likelihood of unintentionally overloading a template name somewhat.

Conversely, it is also imaginable to allow a form of overloading of class templates and variable templates. Here is an example:

// invalid overloading of class templates
template<typename T1, typename T2> class Pair;
template<int N1, int N2> class Pair;

However, there doesn’t seem to be a pressing need for such a mechanism.

1 This definition differs from that given in the C++ standard, but its consequences are equivalent.

2 Alias templates are the only form of template that cannot be specialized, either by a full specialization or a partial specialization. This restriction is necessary to make the use of template aliases transparent to the template argument deduction process Section 15.11 on page 312.

3 The same prefix is also needed to declare full function template specializations. Earlier designs of the C++ language did not include this prefix, but the addition of member templates required additional syntax to disambiguate complex specialization cases.