12.3 Explicit Specialization

Ru-Brd

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 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 and function templates can be fully specialized. So can members of class templates that may be defined outside the body of a class definition (i.e., member functions, nested classes, and static data members ).

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.

12.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 declarator is followed by the template arguments for which the specialization is declared. The following example illustrates this:

[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.

 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:      typedef int I;  };  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:   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()  {      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 const*);  int main()  {      clear(buffer);  }  //   Translation unit 2:  template<typename T>  class Danger;  template<>  class Danger<void> {    public:      enum { max = 100; };  };  void clear(char const* buf)  {  // mismatch in array bound!  for(intk=0;k<Danger<void>::max; ++k) {          buf[k] = ' 
  //   Translation unit 1:  template<typename T> class Danger { public: enum { max = 10; }; }; char buffer[Danger<void>::max];  // uses generic value  extern void clear(char const*); int main() { clear(buffer); }  //   Translation unit 2:  template<typename T> class Danger; template<> class Danger<void> { public: enum { max = 100; }; }; void clear(char const* buf) {  // mismatch in array bound!  for(intk=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.

12.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;  }  template<typename T>  int g(T, T x = 42)  {      return x;  }  template<> int g(int, int y)  {      return y/2;  }  int main()  {      std::cout << f(0) << std::endl;  // should print  21  } 

A full specialization is in many ways similar to a normal declaration (or rather, a normal re declaration). 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 template g in the previous example would therefore typically be organized in two files. The interface file might look as follows:

 #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 may read:

 #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.

12.3.3 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 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()  {      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(); 

The attentive reader might point out that the nondefining declaration of the full specialization of Outer<void>::code has exactly the same syntax as that required to provide a definition to be initialized with a default constructor. This is indeed so, but such declarations are always interpreted as nondefining declarations.

Therefore, there is no way to provide a definition for the full specialization of a static data member with a type that can only be initialized using a default constructor!

 class DefaultInitOnly {    public:      DefaultInitOnly() {      }    private:      DefaultInitOnly(DefaultInitOnly const&);  // no copying possible  };  template<typename T>  class Statics {    private:      T sm;  };  // the following is a declaration;   // no syntax exists to provide a definition  template<>  DefaultInitOnly Statics<DefaultInitOnly>::sm; 

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; };  }; 
Ru-Brd


C++ Templates
C++ Templates: The Complete Guide
ISBN: 0201734842
EAN: 2147483647
Year: 2002
Pages: 185

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net