Previous Next Contents Index Doc Set Home


Templates

3


This chapter discusses both template use and compilation. It introduces template concepts and terminology in the context of function templates, discusses the more complicated (and more powerful) class templates, and the nested use of templates. Also discussed are the emerging standard for template specialization and file organization in the presence of templates. Other topics include: the template compilation process, instance linkage options, automatic instance consistency, and searching for template definitions.

The purpose of templates is to enable programmers to write a single body of code that applies to a wide range of types in a type-safe manner. Before reading this chapter, you should be familiar with the discussion of templates in C++ Annotated Reference Manual.

The C++ compiler's implementation of templates is based on the C++ Annotated Reference Manual with elements from the emerging ISO C++ standard.

The C++ compiler's implementation of templates is different from that of AT&T's Cfront compiler. See "Compile-Time Versus Link-Time Instantiation" on page 84. For additional information, refer to Appendix A, "Migration Guide".


Function Templates

A function template describes a set of related functions. That set is parameterized by types.

Template Declaration

Templates must be declared before they can be used. A declaration, as in the following example, provides enough information to use the template, but not enough information to implement the template.

template <class Number> Number twice( Number original );

Number is a template parameter; it specifies the range of functions that the template describes. More specifically, Number is a template type parameter, and its use within template declarations and definitions stands for some to-be-determined type.

Template Definition

If a template is declared, it must also be defined. A definition provides enough information to implement the template. The following example defines the template declared in the previous example.

template <class Number> Number twice( Number original )
    { return original + original; }

Because template definitions often appear in header files, a template definition may be repeated several times. All definitions, however, must be the same. This restriction is called the One-Definition Rule.

Template Use

Once declared, templates may be used like any other function. Their use consists of naming the template and providing template arguments corresponding to the template's parameters. For example, you may use the previously declared template as follows, explicitly specifying the template argument.

double example1( double item )
    { return twice<double>( item ); }

In this case, double is the type argument. Often the compiler will be able to infer the template arguments from the function arguments. When this is the case, you may omit argument specifications, as in:

double example2( double item )
    { return twice( item ); }

If the compiler is unable to uniquely infer template arguments from the function arguments, it will issue an error. To correct the problem, simply specify the template arguments.

Template Instantiation

The process of instantiation involves generating a concrete function (instance) for a particular combination of template arguments. For example, the compiler will generate a function for twice<int> and a different function for twice<double>. Instantiation may be implicit or explicit.

Implicit Instantiation

The use of a template function introduces the need for an instance. If that instance does not already exist, the compiler will implicitly instantiate the template for that combination of template arguments.

Explicit Instantiation

The compiler will implicitly instantiate templates only for those combinations of template arguments that are actually used. This approach may be inappropriate for the construction of libraries that provide templates. C++ provides a facility to explicitly instantiate templates, as in the following example:

template float twice<float>( float original );

Template parameters and arguments may be omitted when the compiler can infer them, as in:

template int twice( int original );


Class Templates

A class template describes a set of related classes, or data types. That set is parameterized by types, by integral values, and/or by some variable references. Class templates are particularly useful in describing generic, but type-safe, data structures.

Template Declaration

A class template declaration must declare the class data and function members, as in the examples below. In these examples, template arguments are Size, an unsigned int, and Elem, a type.

template <class Elem> class Array {
        Elem* data;
        int size;
    public:
        Array( int sz );
        int GetSize( );
};



template <unsigned Size> class String {
         char data[Size];
        static int overflow;
    public:
        String( char *initial );
        int length( );
};

Template Definition

The definition of a class template consists of a template definition for its member functions, and for its static data members. Dynamic (non-static) data members are sufficiently defined by the template declaration.

Function Members

template <class Elem> Array<Elem>::Array( int sz )
    { size = sz; data = new Elem[ size ]; }

template <class Elem> int Array<Elem>::GetSize( )
    { return size; }



template <unsigned Size> int String<Size>::length( )
    { int len = 0;
      while ( len < Size && data[len] != '\0' ) len++;
      return len; }
 
template <unsigned Size> String<Size>::String( char *inital )
    { strncpy( data, initial, Size );
      if ( length( ) == Size ) overflow = 1; }

Static Data Members

template <unsigned Size> int String<Size>::overflow = 0;

Static data member definitions must always provide an initial value, otherwise, the statement is interpreted as a declaration.

Template Use

A template class may be used wherever a type may be used. Specifying a template class consists of providing the template name and the template arguments corresponding to the template parameters. The following examples define two variables, one of array and one string.

Array<int> int_array( 100 );



String<8> short_string( "hello" );

You may use template class member functions as you would any other member function. For example:

int x = int_array.GetSize( );



int x = short_string.length( );

Instantiation

The process of instantiation involves generating a concrete class (instance) for a particular combination of template arguments. For example, the compiler will generate a class for Array<int> and a different class for Array<double>. The new classes are defined by substituting the template arguments for the template parameters in the definition of the template class. In the Array<int> example, shown in the preceding "Template Declaration", "Template Definition", and "Template Use" sections, the compiler substitutes int wherever Elem appears.

When instantiating a template class, the compiler must instantiate the function members and the static data members. The dynamic data members are not part of the instantiation; they are part of the variable declared with the template class as its type.

Implicit Instantiation

The use of a template class introduces the need for an instance. If that instance does not already exist, the compiler will generate it.

Explicit Instantiation

Implicit instantiation may be inappropriate when constructing libraries that provide templates. Templates may be explicitly instantiated, as in:

template class Array<char>;



template class String<19>;

When the programmer explicitly instantiates a class, all of its member functions are also instantiated.

Whole Class Instantiation

When the compiler implicitly instantiates a template class, it instantiates the static data members, the constructor, and the destructor. However, the compiler does not implicitly instantiate any other member function unless the function is explicitly referenced. To force the compiler to instantiate all member functions when implicitly instantiating a class, use the
-template=wholeclass compiler option. To turn this option off, specify
-template=no%wholeclass,which is the default.


Nested Template Use

Templates may be used (though not defined) in a nested manner. This is particularly useful in defining generic functions over generic data structures, as is done in the emerging Standard Template Library. For example, a template sort function may be declared over a template array class:

template <class Elem> void sort( Array<Elem> );

and defined as:

template <class Elem> void sort( Array<Elem> store )
    { int num_elems = store.GetSize( );
      for ( int i = 0;  i < num_elems-1;  i++ )
          for ( int j = i+1;  j < num_elems;  j++ )
              if ( store[j-1] > store[j] )
                  { Elem temp = store[j];
                    store[j] = store[j-1];
                    store[j-1] = temp; } }

This example defines a sort function over the predeclared Array class template objects. The following examples shows the actual use of the sort function.

Array<int> int_array( 100 );   // construct our array of ints
sort<int>( int_array );        // sort it


Template Specialization, Standard Method

There may be significant performance advantages to treating some combinations of template arguments as a special case, as in the examples below for twice. Alternatively, a template description may fail to work for a set of its possible arguments, as in the examples below for sort. Template specialization enables the definition of alternate implementations for a given combination of actual template arguments. The template specialization overrides the default instantiation.

Declaration

A specialization must be declared before any use of that combination of template arguments. The following examples declare specialized implementations of twice and sort.

template <> unsigned twice<unsigned>( unsigned original );



template <> sort<char*>( Array<char*> store );

The template arguments may be omitted if the compiler can unambiguously determine them. For example:

template <> unsigned twice( unsigned original );



template <> sort( Array<char*> store );

Definition

All template specializations declared must also be defined. The following examples define the functions declared above.

template <> unsigned twice<unsigned>( unsigned original )
    { return original << 1; }



#include <string.h>
template <> void sort<char*>( Array<char*> store )
    { int num_elems = store.GetSize( );
      for ( int i = 0;  i < num_elems-1;  i++ )
          for ( int j = i+1;  j < num_elems;  j++ )
              if ( strcmp( store[j-1], store[j] ) > 0 )
                  { char *temp = store[j];
                    store[j] = store[j-1];
                    store[j-1] = temp; } }

Use and Instantiation

Specializations are used and instantiated just like any other template, except that the definition of a completely specialized template is also an instantiation.


Template File Organization

There are two primary organizations of template files, the definitions-included and the definitions-separated. The definitions-included organization enables more control over template compilation, and is recommended for that reason.

Definitions-Included

When the declarations and definitions for a template are contained within the file that uses the template, the file organization is definitions-included. For example:

main.cc

template <class Number> Number twice( Number original );
template <class Number> Number twice( Number original )
    { return original + original; }
int main( )
    { return twice<int>( -3 ); }

When the file that uses a template includes a file containing both the template declaration and the template definition, the file has the definitions-included organization. For example:

twice.h

#ifndef TWICE_H
#define TWICE_H
template <class Number> Number twice( Number original );
template <class Number> Number twice( Number original )
    { return original + original; }
#endif
main.cc

#include "twice.h"
int main( )
    { return twice<int>( -3 ); }

It is very important to make template headers idempotent. That is, when a header is included many times, its effect is always the same. This is easiest to accomplish when the header turns itself off with #define and #ifndef, as in the preceding example.

Definitions-Separate

When the file that uses a template includes a file containing only the declaration of the template, and not the definition, the template file has the definitions-separate organization. The definition must appear in another file. For example:

twice.h

template <class Number> Number twice( Number original );
twice.cc

#include "twice.h"
template <class Number> Number twice( Number original )
    { return original + original; }
main.cc

#include "twice.h"
int main( )
    { return twice<int>( -3 ); }

Because of the separation of header and template source files, you must be very careful in file construction, placement, and naming. You may also need to explicitly identify the location of the source file to the compiler.


Potential Problem Areas

This section describes potential problem areas in using templates.

Non-Local Name Resolution and Instantiation

Some names used within a template definition may not be defined by the template arguments or within the template itself. If so, the compiler resolves the name from the scope enclosing the template which could be the context at the point of definition, or at the point of instantiation. These alternatives may yield different resolutions. Name resolution is complex, and currently under debate in the C++ standards committee. Consequently, you should not rely on non-local names, except those provided in a pervasive global environment. In other words, a name may have different meanings in different places; use only non-local names that are declared everywhere and that mean the same thing everywhere. For example:

use1.cc

typedef int intermediary;
int temporary;
template <class Source, class Target>
Target converter( Source source )
       { temporary = (intermediary)source; 
       return (Target)temporary; }
use2.cc

typedef double intermediary;
unsigned int temporary;
template <class Source, class Target>
Target converter( Source source )
       { temporary = (intermediary)source;
       return (Target)temporary; }

In this example, the template function converter uses the non-local names intermediary and temporary. These names have different definitions in the environment of each template, and will probably yield different results under different compilers. In order for templates to work reliably, all non-local names (intermediary and temporary) must have the same definition everywhere.

A common use of non-local names is the use of the cin and cout streams within a template. Few programmers really want to pass the stream as a template parameter, so they refer to a global variable. However, cin and cout must have the same definition everywhere.

Local Types as Template Arguments

The template instantiation system relies on type-name equivalence to determine which templates need to be instantiated or reinstantiated. Thus local types can cause serious problems when used as template arguments. Beware of creating similar problems in your code. For example:

array.h

template <class Type> class Array {
        Type* data;
        int   size;
    public:
        Array( int sz );
        int GetSize( );
};

array.cc

#include "array.h"
template <class Type> Array<Type>::Array( int sz )
    { size = sz; data = new Type[size]; }
template <class Type> int Array<Type>::GetSize( )
    { return size;}

file1.cc

#include "array.h"
struct Foo { int data; };
Array<Foo> File1Data;

file2.cc

#include "array.h"
struct Foo { double data; };
Array<Foo> File2Data;

The Foo type as registered in file1.cc is not the same as the Foo type registered in file2.cc. Using local types in this way could lead to errors and unexpected results.

Friend Declarations of Template Functions

The template instantiation system requires that a declaration follow the search rules in locating template definitions, that is, a declaration must precede an instantiation. This declaration must be a true declaration, and not a friend declaration. For example:

array.h

#ifndef _ARRAY_H
#define _ARRAY_H
#include <iostream.h>
template <class T> class array {
    protected:
        int size;
    public:
        array( );
        friend ostream& operator<<( ostream&, const array<T>& );
};
#endif // _ARRAY_H 

array.cc

#include <iostream.h>
#include <stdlib.h>
#include "array.h"
template <class T> array<T>::array( )
    { size = 1024; }
template <class T>
ostream& operator<<( ostream& out, const array<T>& rhs )
    { return out << "[" << rhs.size << "]"; }

main.cc

#include <iostream.h>
#include "array.h"
int main( )
    { cout << "creating an array of int... " << flush;
      array<int> foo;
      cout << "done\n";
      cout << foo << endl;
      return 0; }

When the compilation system attempts to link the produced object files, it will generate an undefined error for the operator<< function, which is not instantiated. There is no error message during compilation because the compiler will have read:

friend ostream& operator<<(ostream&, const array<T>&);

as the declaration of a normal function that is a friend of the array class. To properly instantiate the functions, the following declaration must be added outside the array class declaration:

template <class T>
ostream& operator<<( ostream&, const array<T>& );

This declaration guarantees that the function is instantiated for each object of type array<T>.


Template Compilation

Template compilation is a complicated process because the compiler must instantiate templates at nearly arbitrary times during compilation, and because it may need to search for template definitions among several files. This section describes the major components of template compilation.

Verbose Compilation

The C++ compiler will notify the users of significant events during template compilation when given the flag -verbose=template. Conversely, the compiler will not notify users when given -verbose=no%template, which is the default. The +w option may give other indications of potential problems when template instantiation occurs.

Template Database

The template database is a directory containing all configuration files needed to handle and instantiate the templates required by your program. It also acts as a repository for all generated object files containing templates.

The purpose of the template database is to ensure that there is exactly one instance for every combination of template and arguments; that is, to ensure that there are no redundant template instances. The database creates instances only when necessary, ensuring that all instances are up-to-date.

The template database is only used during external instantiation (see "External Instance Linkage" on page 87).

The template database is contained, by default, in the subdirectory Templates.DB within the current directory. You may specify the directory with the -ptrdirectory option. If the directory does not exist, and the compiler needs to instantiate a template, the directory is created for you.

Multiple Template Databases

You may specify multiple databases by giving multiple -ptr options. When specifying multiple databases, the first one to specify is your working, writable database. All others are read-only. To avoid confusion in multiple-database environments, make the first entry your current working directory. For example:

CC -ptr. -ptr/usr/lib/Templates.DB -o main main.cc


Note - It is highly recommended that you do not rely on the use of the -ptr directive as its use may become obsolete in future releases. While alternatives will be available, the direct use of -ptr might cause compiler performance degradation in the future.

Using the Same Template Database for Multiple Targets

If you use the same template database for multiple targets, inconsistent results can occur. However, if you choose to build multiple targets in the same work directory, tdb_link notes this fact during parallel builds and implements a locking mechanism on the primary working database. If two or more links are attempted on the same database, tdb_link initiates a wait until the lock is cleared by the other compilation.

The ptclean Command

Changes in your program can render some instantiations superfluous, thus wasting storage space. The ptclean command clears out the template database, removing all instantiations, temporary files, and dependency files. Instantiations will be recreated when, and only when, needed.

Options File

The template options file, Template.opt, is a user-provided optional file that contains options needed to locate template definitions and to control instance recompilation. In addition, the options file provides features for controlling template specialization and explicit instantiation, although the user is discouraged from using them because the C++ compiler now supports the syntax required to declare specializations and explicit instantiation in the source code.

The options file is an ASCII text file containing a number of entries. An entry consists of a keyword followed by expected text and terminated with a semicolon (;). Entries can span multiple lines, although the keywords cannot be split.

Comment Entries

Comments start with a # character and extend to the end of the line. Text within a comment is ignored.

# Comment text is ignored until the end of the line.

Sharing Options Files

You may share options files among several template databases by including the options files. This facility is particularly useful when building libraries containing templates. During processing, the specified options file is textually included in the current options file. You can have more than one include statement and place them anywhere in the options file. The options files can also be nested.

include "options-file";

Template Definition Location and Consistency Entries

The extensions and definition options file entries are described in "Template Definition Searching" on page 87.

Template Specialization Entries

The special options file entries are described in "Template Specialization, Deprecated Method" on page 92.

Compile-Time Versus Link-Time Instantiation

Instantiation is the process by which a C++ compiler creates a usable function or object from a template. Two common methods of template instantiation are compile-time instantiation and link-time instantiation.

C++ Compile-Time Instantiation

C++ 4.2 uses compile-time instantiation, which forces instantiations to occur when the reference to the template is being compiled.

The advantages of compile-time instantiation are:

Templates may be instantiated multiple times if source files reside in different directories, or if you use libraries with template symbols.

Cfront Link-Time Instantiation

Cfront uses the link-time method, which uses this algorithm:

1. Compile all user source files.

2. Using the prelinker, ptlink, link all object files created in step 1 into a partially linked executable.

3. Examine the link output and instantiate all undefined functions for which there are matching templates.

4. Link all created templates along with the partially linked executable files from step 2.

5. As long as there are undefined functions for which there are matching template functions, repeat steps 3 through 4.

6. Perform the final pass of the link phase on all created object files.

The main advantage of link-time instantiation is that no special outside support is required to handle specializations (user-provided functions intended to override instantiated template functions). Only those functions that have not been defined in the user source files become targets of instantiation by the compiler.

The two main disadvantages of link-time instantiation are:


Template Instance Linkage

You may instruct the compiler to use one of three different instance linkage methods: static, global, or external. Static instances are suitable for very small programs or debugging. Global instances are suitable for some library construction. External instances are suitable for all development and provide the best overall template compilation. External instances are the default.


Note - If you wish to create a library that contains all instances of the templates that it uses, specify the -xar option when creating the library. Do not use the ar command. For example: CC -xar libmain.a a.o b.o c.o

Static Instance Linkage

Under static instance linkage, all instances are placed within the current compilation unit. As a consequence, templates will be re-instantiated during each re-compilation; instances are not saved to the template database.

Template instances receive static linkage. These instances will not be visible or usable outside of the current compilation unit.

Specify static instance linkage with the -instances=static option.

Static instance linkage may only be used with the definitions-included template organization. The compiler will not search for definitions.

Templates may have identical instantations in several object files. This results in unnecessarily large programs. Static instance linkage is therefore suitable only for small programs, where templates are unlikely to be multiply instantiated.

Compilation is potentially faster with static instance linkage, so static linkage may also be suitable during fix-and-continue debugging.

Global Instance Linkage

Under global instance linkage, all instances are placed within the current compilation unit. As a consequence, templates will be re-instantiated during each re-compilation; they are not saved to the template database.

Template instances receive global linkage. These instances will be visible and usable outside of the current compilation unit.

Specify global instance linkage with the -instances=global option.

Global instance linkage may only be used with the definitions-included template organization. The compiler will not search for definitions.

Templates may have identical instantations in several object files. This results in multiple definitions conflicts during linking. Global linkage is therefore suitable only when it is known that instances will not be repeated, such as when constructing libraries with explicit instantiation.

External Instance Linkage

Under external instance linkage, all instances are placed within the template database. Templates will be re-instantiated only when necessary.

Instances are referenced from the current compilation unit with external linkage. Instances are defined within the template database with global linkage. The compiler ensures that exactly one template instance exists; instances will be neither undefined nor multiply defined.

Specify external linkage with the -instances=extern option. This option is the default.

Because instances are stored within the template database, you must use the CC command to link C++ objects that use external instance linkage into programs.

If you wish to create a library that contains all instances that it uses, specify the -xar option when creating the library. Do not use the ar command. For example:

CC -xar libmain.a a.o b.o c.o


Template Definition Searching

When using the definitions-separate template file organization, template definitions are not available in the current compilation unit, and the compiler must search for the definition. This section describes the controls on that search. Definition searching is somewhat complex and prone to error. Therefore, you should use the definitions-included template file organization, which avoids definition searching altogether.

Source File Location Conventions

Without specific directions as provided with an options file (see below), the compiler uses a Cfront-style method to locate template definition files. This method requires that the template definition file contain the same base name as the template declaration file, and that it also be on the current include path. For example, if the template function foo() is located in foo.h, the matching template definition file should be named foo.cc or some other recognizable source-file extension. The template definition file must be located in one of the normal include directories or in the same directory as its matching header file.

Source File Extensions

You can specify different source file extensions for the compiler to search for when it is using its default Cfront-style source-file-locator mechanism. The format is:

extensions "ext-list";

The ext-list is a list of extensions for valid source files in a space-separated format such as:

extensions ".CC .c .cc .cpp";

In the absence of this entry from the options file, the valid extensions for which the compiler searches are .cc, .c, .cpp, .C, and .cxx. (See "Options File" on page 83.)

Definitions Search Path

As an alternative to the normal search path set with -I, you may specify a search directory for template definition files with the option -ptidirectory. Multiple -pti flags will define multiple search directories, i.e. a search path. If -ptidirectory is used, the compiler looks for template definition files on this path and ignores the -I flag. It is recommended, however, that you use the -I flag instead of the -ptidirectory flag, since the -ptidirectory flag complicates the search rules for source files.

Options File Definition Entries

Definition source file locations may be explicitly specified with the definition option file entry. The definition entry is provided for those cases when the template declaration and definition file names do not follow the standard Cfront-style conventions. See "Source File Location Conventions" on page 88. The entry syntax is:

definition  name in "file-1",[ "file-2" ..., "file-n"]  [nocheck "options"];

The name field indicates the template for which the option entry is valid. Only one definition entry per name is allowed. That name must be a simple name; qualified names are not allowed. Parentheses, return types and parameter lists are not allowed. Regardless of the return type or parameters, only the name itself counts. As a consequence, a definition entry may apply to several (possibly overloaded) templates.

The "file-n" list field specifies in which files the template definitions may be found. Search for the files using the definition search path described in the previous section, "Definitions Search Path."The file names must be enclosed in quotes (" "). Multiple files are available because the simple template name may refer to different templates defined in different files, or because a single template may have definitions in multiple files. For example, if func is defined in three files, then those three files must be listed in the definition entry.

The nocheck field is described in "Template Instance Automatic Consistency" on page 91.

In the following example, the compiler locates the template function foo in foo.cc, and instantiates it. In this case, the definition entry is redundant with the default search.

foo.cc

template <class T> T foo( T t ) { }

Template.opt

definition foo in "foo.cc";

The following example shows the definition of static data members and the use of simple names.

foo.h

template <class T> class foo { static T* fooref; };

foo_statics.cc

#include "foo.h"
template <class T> T* foo<T>::fooref = 0

Template.opt

definition fooref in "foo_statics.cc";

The name provided for the definition of fooref is a simple name and not a qualified name (such as foo::fooref). The reason for the definition entry is that the file name is not foo.cc (or some other recognizable extension) and cannot be located using the default Cfront-style search rules. See "Source File Location Conventions" on page 88.

The following example shows the definition of a template member function. As the example shows, member functions are handled exactly like static member initializers.

foo.h

template <class T> class foo { T* foofunc(T); };

foo_funcs.cc

#include "foo.h"
template <class T> T* foo<T>::foofunc(T t) {}

Template.opt

definition foofunc in "foo_funcs.cc";

The following example shows the definition of template functions in two different source files.

foo.h

template <class T> class foo {
    T* func( T t );
    T* func( T t, T x );
};

foo1.cc

#include "foo.h"
template <class T> T* foo<T>::func( T t ) { }

foo2.cc

#include "foo.h"
template <class T> T* foo<T>::func( T t, T x ) { }

Template.opt

definition func in "foo1.cc", "foo2.cc";

In this example, the compiler must be able to find both definitions of the overloaded function func(). The definition entry tells the compiler where to find the appropriate function definitions.


Template Instance Automatic Consistency

The template database manager ensures that the state of the files in the database is consistent and up-to-date with your source files.

For example, if your source files are compiled with -g (debugging on), the files you need from the database are also compiled with -g.

In addition, the template database tracks changes in your compilation. For example, if the first time you compile your sources, you have the -DDEBUG flag set to define the name DEBUG, the database tracks this. If you omit this flag on a subsequent compile, the compiler reinstantiates those templates on which this dependency is set.

Options File Nocheck Field

Sometimes recompiling is unnecessary when certain compilation flags change. You can avoid unnecessary recompilation using the nocheck field of the definition option file entry, which tells the compiler and template database manager to ignore certain options when checking dependencies. If you do not want the compiler to reinstantiate a template function because of the addition or deletion of a specific command-line flag, you should add that flag here. The entry syntax is:

definition  name in "file-1"[, "file-2" ..., "file-n"]  [nocheck "options"];

The name and file list fields are described in "Template Definition Searching" on page 87. The options themselves must be enclosed in quotes (" ").

In the following example, the compiler locates the template function foo in foo.cc, and instantiates it. If a reinstantiation check were later required, the compiler would ignore the -g option.

foo.cc

template <class T> T foo( T t ) {}

Template.opt

 definition foo in "foo.cc" nocheck "-g";


Template Specialization, Deprecated Method

Until recently, the C++ language provided no mechanism for specializing templates, so each compiler provided its own mechanism. This section describes the specialization of templates using the mechanism of previous versions of the C++ compilers.


Note - Since this mechanism is supported in this release of the C++ compiler, but may not be supported in future releases, it is referred to as "deprecated." Avoid this mechanism for new code, and migrate existing code to the standard mechanism. (See "Template Specialization, Standard Method" on page 74.)

Specialized Template Definitions

The following example shows a specialized constructor that creates an array of character strings of fixed size:

void Array<char*>::Array( int sz )
    { size = sz;
      data = new char * [size];
      for ( int i = 0; i < size; i++ ) data[i] = new char[128]; }

In this example, the constructor allocates each char pointer in the array. Because a given executable may span many object files and libraries, the compiler does not know whether the specific function called exists elsewhere in the compilation environment. Specializations can be registered in the options file, as discussed below. Given the preceding code, the following takes place:

Array<int>   IntArray( 10 );      // The compiler instantiates an
// Array of ints using the default
// constructor
Array<char*> CharStarArray( 10 ); // The compiler uses the
// specialized constructor

The following example shows specialization of template functions:

template <class T> void func ( T t ) { }    // A template function
void func( double ) { }                // A specialization of func()
int main( )
    { func(10);    // The compiler-instantiated func<int> is used
      func(1.23);  // The user-provided func(double) is used
    }

Options File Specialization Entries

The special entry specifies to the compiler that a given function is a specialization and should not be instantiated when encountered. When using the compile-time instantiation method, preregister specializations with an entry in the options file. The syntax is:

special declaration;

The declaration is a legal C++-style declaration without return types. Overloading of the special entry is allowed. For example:

foo.h:

template <class T> T foo( T t ) { };

main.cc:

#include "foo.h"

Template.opt:

special foo(int);

The preceding options file informs the compiler that the template function foo() should not be instantiated for the type int, and that a specialized version is provided by the user. Without that entry in the options file, the function may be instantiated unnecessarily, resulting in errors:

foo.h

template <classT> T foo( T t ) { return t + t; }

file.cc

#include "foo.h"
int func( ) { return foo( 10 ); }

main.cc

#include "foo.h"
int foo( int i ) { return i * i; } // the specialization
int main( ) { int x = foo( 10 ); int y = func(); return 0; }

In the preceding example, when the compiler compiles main.cc, the specialized version of foo is correctly used because the compiler has seen its definition. When file.cc is compiled, however, the compiler instantiates its own version of foo because it doesn't know foo exists in main.cc. In most cases, this process results in a multiply-defined symbol during the link, but in some cases (especially libraries), the wrong function may be used, resulting in runtime errors. If you use specialized versions of a function, you should register those specializations.

The special entries can be overloaded, as in this example:

foo.h

template <classT> T foo( T t ) {}

main.cc

#include "foo.h"
int   foo( int   i ) {}
char* foo( char* p ) {}

Template.opt

special foo(int);
special foo(char*);

To specialize a template class, include the template arguments in the special entry:

foo.h

template <class T> class Foo { ... various members ... };

main.cc

#include "foo.h"
int main( ) { Foo<int> bar; return 0; }

Template.opt

special class Foo<int>;

If a template class member is a static member, you must include the keyword static in your specialization entry:

foo.h

template <class T> class Foo { public: static T func(T); };

main.cc

#include "foo.h"
int main( ) { Foo<int> bar; return 0; }

Template.opt

special static Foo<int>::func(int);




Previous Next Contents Index Doc Set Home