Chapter 4. Using Templates

This chapter discusses the Silicon Graphics C++ implementation of templates. It compares the Silicon Graphics implementation to those of the Borland C++ and Cfront compilers. It contains the following major sections:

For general information about the Standard Template Library (STL) (a library of container classes, algorithms, and iterators for generic programming), see the HTML document available at http://www.sgi.com/Technology/STL/. The MIPSpro 7.3 Release Notes have information about the particular version of the STL being shipped with the 7.3 release.

Template Instantiation

The instantiation of a class template is done as soon as it is needed in a compilation. However, the instantiations of template functions, member functions of template classes, and static data members of template classes (hereafter referred to as template entities) are not necessarily done immediately for the following reasons:

  • You should have only one copy of each instantiated entity across all the object files that make up a program. (This applies to entities with external linkage.)

  • You may write a specialization of a template entity. (For example, you can write a version either of Stack<int>, or of just Stack<int>::push, that replaces the template-generated version. Often, this kind of specialization is a more efficient representation for a particular data type.) When compiling a reference to a template entity, the compiler does not know if a specialization for that entity will be provided in another compilation. The compiler cannot do the instantiation automatically in any source file that references it.

  • You may not compile template functions that are not referenced. Such functions might contain semantic errors that would prevent them from being compiled. A reference to a template class should not automatically instantiate all the member functions of that class.


Note: Certain template entities are always instantiated when used (for example, inline functions).

If the compiler is responsible for doing all the instantiations automatically, it can do so only on a program-wide basis. The compiler cannot make decisions about instantiation of template entities until it has seen all the source files that make up a complete program.

By default, CC performs automatic instantiation at link time. It is also possible for you to instantiate all necessary template entities at compile time using the -ptused option.

Automatic Instantiation

Automatic instantiation enables you to compile source files to object code, link them, run the resulting program, and never worry about how the necessary instantiations are done.

The CC(1) command requires that for each instantiation you have a normal, top-level, explicitly-compiled source file that contains both the definition of the template entity and any types required for the particular instantiation.

Meeting Instantiation Requirements

You can meet the instantiation requirements in several ways:

  • You can have each header file that declares a template entity contain either the definition of the entity or another file that contains the definition.

  • When the compiler encounters a template declaration in a header file and discovers a need to instantiate that entity, you can give it permission to search for an associated definition file having the same base name and a different suffix. The compiler implicitly includes that file at the end of the compilation. This method allows most programs written using the Cfront convention to be compiled. See “Implicit Inclusion”.

  • You can make sure that the files that define template entities also have the definitions of all the available types, and add code or #pragma directives in those files to request instantiation of the entities they contain.

Automatic Instantiation Method

  1. The first time the source files of a program are compiled, no template entities are instantiated. However, the generated object files contain information about things that could have been instantiated in each compilation.

  2. When the object files are linked, a program called the prelinker is run. It examines the object files, looking for references and definitions of template entities, and for the added information about entities that could be instantiated.

  3. If the prelinker finds a reference to a template entity for which there is no definition anywhere in the set of object files, it looks for a file that indicates that it could instantiate that template entity. When it finds such a file, it assigns the instantiation to it. The set of instantiations assigned to a given file (for example, abc.C) is recorded in an associated .ii file (for example, abc.ii). All .ii files are stored in a directory named ii_files created within your object file directory.

  4. The prelinker then executes the compiler again to recompile each file for which the .ii file was changed. (The .ii file contains enough information to allow the prelinker to determine which options should be used to compile the same file.)

  5. When a file is compiled, the compiler reads the .ii file for that file and obeys the instantiation requests therein. It produces a new object file containing the requested template entities (and all the other things that were already in the object file).

  6. The prelinker repeats steps 3-5 until there are no more instantiations to be adjusted.

  7. The object files are linked.

Details of Automatic Instantiation

Once the program has been linked correctly, the .ii files contain a complete set of instantiation assignments. From then on, whenever source files are recompiled, the compiler will consult the .ii files and do the indicated instantiations as it does the normal compilations. Except in cases where the set of required instantiations changes, the prelink step will find that all the necessary instantiations are present in the object files and that no instantiation assignment adjustments need be done. This is true even if the entire program is recompiled.

If you provide a specialization of a template entity somewhere in the program, the specialization will be seen as a definition by the prelinker. Since that definition satisfies whatever references there might be to that entity, the prelinker sees no need to request an instantiation of the entity. If the programmer adds a specialization to a program that has previously been compiled, the prelinker notices that too and removes the assignment of the instantiation from the proper .ii file.

The .ii files should not, in general, require any manual intervention. The only exception is if the following conditions are met:

  • A definition is changed in such a way that some instantiation no longer compiles. (It generates errors.)

  • A specialization is simultaneously added in another file.

  • The first file is recompiled before the specialization file and is generating errors.

The .ii file for the file generating the errors must be deleted manually to allow the prelinker to regenerate it.

If the prelinker changes an instantiation assignment, it will issue a message, such as the following:

C++ prelinker: f__10A__pt__2_iFv assigned to file test.o
C++ prelinker: executing: usr/lib/DCC/edg-prelink -c test.c

The name in the message is the mangled name of the entity. These messages are printed if you use the -ptv option.

The automatic instantiation scheme can coexist with partial explicit control of instantiation by the programmer, through the use of #pragma directives or command-line specification of the instantiation mode.

The automatic instantiation mode can be disabled by using the -no_prelink option.

If automatic instantiation is turned off, the following conditions are true:

  • The extra information about template entities that could be instantiated in a file is not put into the object file.

  • The .ii file is not updated with the command line.

  • The prelinker is not invoked.

Implicit Inclusion

For the best results, you must include all the template implementation files in your source files. Since most Cfront users do not do this, the compiler attempts to find unincluded template bodies automatically. For example, suppose that the following conditions are all true:

  • Template entity ABC::f is declared in the xyz.h file.

  • An instantiation of ABC::f is required in a compilation.

  • No definition of ABC::f appears in the source code processed by the compilation.

In this case, the compiler looks to see if the source file xyz.n exists. (By default, the list of suffixes tried for n is .c, .C, .cpp, .CPP, .cxx, .CXX, and .cc.) If so, the compiler processes it as if it were included at the end of the main source file.

Implicit inclusion works well alongside automatic instantiation, but the two are independent. They can be enabled or disabled independently, and implicit inclusion is still useful when automatic instantiation is not done. Implicit inclusion can be disabled with the -no_auto_include option.

Explicit Instantiation

The CC command instantiates all templates at compile time if you specify the -ptused option. The compiler produces larger object files because it stores duplicate instantiations in the object files. Duplicate copies may not be removed by the linker and may exist in the final executables.

The CC template instantiation mechanism also correctly handles static data members when you use the -ptused option. Static data members that must be dynamically initialized may be instantiated in multiple compilation units. However, the dynamic initialization takes place only once. This is implemented by using a flag which is set the first time a static data member is initialized. This flag prevents further attempts to initialize it dynamically.

The -ptused option is acceptable for most small- or medium-sized applications. There are some drawbacks listed below:

  • Instantiating everything produces large object files.

  • Although duplicate code is removed, the associated debug information is not removed, producing large executables.

  • If you change a template body, you must recompile every file that contains an instantiation of this body. (The easiest way to do this is for you to use the make(1) command in conjunction with the -MDupdate option. See the CC(1) reference page and “Limitations on Template Instantiation”, for more information.)

  • If you plan on specializing a template function instantiation, you may have to set the #pragma do_not_instantiate directive if it is likely that the compiler-generated instantiation will contain syntax errors.

  • Data is not removed, so there are multiple copies of static data members.

You can exercise finer control over exactly what is instantiated in each object file by using #pragma directives and command-line options.

Command Line Options for Template Instantiation

You can use command-line options to control the instantiation behavior of the compiler. These options are divided into four sets of related options, as shown in the following list. You use one option from each category: options from the same category are not used together. (For example, you cannot specify -ptnone in conjunction with -ptused.)

  • -ptnone (the default), -ptused, and -ptall. (Automatic template instantiation should make the use of -ptused and -ptall unnecessary in most cases.)

  • -prelink (the default) and -no_prelink.

  • -auto_include and -no_auto_include.

  • -ptv.

The following command line options control instantiation behavior of the compiler:

-ptnone 

None of the template entities are instantiated. If automatic instantiation is on (in other words, -prelink), any template entities that the prelinker instructs the compiler to instantiate are instantiated.

-ptused 

Any template entities used in this compilation unit are instantiated. This includes all static members that have template definitions. If you specify -ptused, automatic instantiation is turned off by default. If you enable automatic instantiation explicitly (with -prelink), any additional template entities that the prelinker instructs the compiler to instantiate are also instantiated.

-ptall 

Any template entities declared or referenced in the current compilation unit are instantiated. For each fully instantiated template class, all its member functions and static data members are instantiated whether or not they are used.


Note: The use of the -ptall option is being deprecated in the MIPSpro compilers.

Nonmember template functions are instantiated even if the only reference was a declaration. If you specify -ptall, automatic instantiation is turned off by default. If you enable automatic instantiation explicitly (with -prelink), any additional template entities that the prelinker instructs the compiler to instantiate are also instantiated.

-prelink 

Instructs the compiler to output information from the object file and an associated .ii file to help the prelinker determine which files should be responsible for instantiating the various template entities referenced in a set of object files.

When -prelink is on, the compiler reads an associated .ii file to determine if any template entities should be instantiated. When -prelink is on and a link is being performed, the compiler calls a template prelinker. If the prelinker detects missing template entities, they are assigned to files (by updating the associated .ii file), and the prelinker recompiles the necessary source files.

-no_prelink 

Disables automatic instantiation. Instructs the compiler to not read a .ii file to determine which template entities should be instantiated. The compiler will not store any information in the object file about which template entities could be instantiated. This option also directs the compiler to not invoke the template prelinker at link time.

This is the default mode if -ptused or -ptall is specified.

-auto_include 

Instructs the compiler to implicitly include template definition files if such definitions are needed. (See “Implicit Inclusion”.)

-no_auto_include 

Disables implicit inclusion of template implementation files. (See “Implicit Inclusion”.)

-ptv 

Puts the template prelinker in verbose mode; when a template entity is assigned to a particular source file, the name of the template entity and source file is printed.


Note: In the case where a single file is compiled and linked, the compiler uses the -ptused option to suppress automatic instantiation.


Command Line Instantiation Examples

This section provides you with typical combinations of command line instantiation options, along with an explanation of what these combinations do and how they may be used.

Although there are many possible combinations of options, the following are the most common combinations:

-ptnone -prelink -auto_include 

This is the default mode, which is suitable for most applications. On the first build of an application, the prelinker determines which source files should instantiate the necessary template entities. On subsequent rebuilds, the compiler automatically instantiates the template entities.

-ptused 

This mode is suitable for small- and medium-sized applications. No prelinker pass is necessary. All referenced template entities are instantiated at compile time. Dynamically initialized static data members are also handled correctly (by using a runtime guard to prevent duplicate initialization of such members).

-ptused -prelink 

Use this combination when you have an archive or dynamic shared object (DSO) that has not been prelinked.

When a DSO is built, it is automatically prelinked. When an archive is built, it is recommended that you run the prelinker on the object files before archiving them. However, there are cases where you may choose not to do so.

For example, if an application is linked using multiple internal DSOs or archives, then you may choose not to prelink each DSO or archive, since that may create multiple instances of some template entities. When building an application using such archives or DSOs, you should use -prelink at compile time, even if the application is being built using -ptused. This is because the object files must contain not only instances of template entities referenced in the compilation units, but also instances of template entities referenced in archives and DSOs.

-ptall -no_prelink 

Use this combination when you are building a library of instantiated templates.

For example, consider if you implement a stack template class containing various member functions. You may choose to provide instantiated versions of these functions for various common types (such as, int and float) and the easiest way of instantiating all member functions of a template is to specify -ptall.

-ptnone -no_prelink 

Use this combination if you are using template entities that are pre-instantiated.

For example, suppose you are using templates and know that all of your referenced template entities have already been pre-instantiated in a library such as one described in the previous example. In this case, you do not need any templates instantiated at compile time, and you should turn off automatic instantiation.

-auto_include 

Use this option if you are using template implementation files that are not explicitly included.

Most source code written for Cfront-style compilers does not usually include template implementation files, because the Cfront prelinker does this automatically. The -auto_include option is the default mode, because you want to compile Cfront-style code, but still instantiate templates at compile time (which implies finding template implementation files automatically).

-no_auto_include 

Use this option if you are using template implementation files that are explicitly included.

Source code written for compilers such as Borland C++ includes all necessary template implementation files. Such source code should be compiled with the -no_auto_include option.

#pragma Directives for Template Instantiation

You can use #pragma directives to control the instantiation of individual or sets of template entities. There are three instantiation #pragma directives:

#pragma instantiate 

Causes a specified entity to be instantiated.

#pragma do_not_instantiate 

Suppresses the instantiation of a specified entity. Typically used to suppress the instantiation of an entity for which a specific definition is supplied.

#pragma can_instantiate 

Allows (but does not force) a specified entity to be instantiated in the current compilation. You can use it in conjunction with automatic instantiation to indicate potential sites for instantiation if the template entity turns out to be required.

The arguments to the instantiation #pragma directives may be any of the following:

  • A template class name, such as A<int>

  • A member function name, such as A<int>::f

  • A static data member name, such as A<int>::i

  • A member function declaration, such as void A<int>::f(int, char)

  • A template function declaration, such as char* f(int, float)

A #pragma directive in which the argument is a template class name (for example, A<int>) is the same as repeating the #pragma directive for each member function and static data member declared in the class.

When you instantiate an entire class, you may exclude a given member function or static data member using the #pragma do_not_instantiate directive as in the following example:

#pragma instantiate A<int>
#pragma do_not_instantiate A<int>::f

You must present the template definition of a template entity in the compilation for an instantiation to occur. (You can also find the template entity with implicit inclusion.) If you request an instantiation by using the #pragma instantiate directive and either no template definition is available or a specific definition is provided, you will receive a link-time error.

For example:

template <class T> void f1(T);
template <class T> void g1(T);
void f1(int) {}
void main() {
int i;
double d;
f1(i);
f1(d);
g1(i);
g1(d);
}
#pragma instantiate void f1(int)
#pragma instantiate void g1(int)

f1(double) and g1(double) are not instantiated (because no bodies were supplied) but no errors are produced during the compilation. If no bodies are supplied at link time, you will receive a linker error.

You can use a member function name (for example, A<int>::f) as a #pragma argument only if it refers to a single user-defined member function. (In other words, not an overloaded function.) Compiler-generated functions are not considered, so a name may refer to a user-defined constructor even if a compiler-generated copy constructor of the same name exists.

You can instantiate overloaded member functions by providing the complete member function declaration. See the following example:

#pragma instantiate char* A<int>::f(int, char*)

The argument to an instantiation #pragma may not be any of the following:

  • A compiler-generated function

  • An inline function

  • A pure virtual function

Specialization

The CC command supports specialization. In template instantiation, you specialize when you define a specific version of a function or static data member.

Because the compiler instantiates everything at compile time when the -ptused option is specified, a specialization is not seen until link time. The linker and runtime loader select the specialization over any non-specialized versions of the function or static data member.

See “#pragma Directives for Template Instantiation”, for information on how to suppress the instantiation of a function. You may find this useful if you intend to provide a specialization in another object file and the non-specialized version cannot be instantiated.

Building Shared Libraries and Archives

When you build a shared library or archive, you should instantiate any template instances that could be needed. You can have the prelinker automatically instantiate all needed templates in either of the following two ways:

  • Build a shared library by using a command such as the following:

    CC -n32 -shared ...

  • Build an archive by using a command such as the following:

    CC -ar ...

Limitations on Template Instantiation

The following subsections discuss the limitations on template instantiation in the Silicon Graphics C++ environment.

Unselected Template Specializations in Archives

A template specialization that exists in an archive may fail to be selected. If you define a specialization within an object file that exists in an archive, and that object file does not satisfy any references (other than the reference to the specialization), then the object file is not selected. Any function generated from a template that appears before the archive is used may not be selected, although a specialization should take precedence over a generated function.

The following conditions must be present for the bug to occur:

  • A template member needs to be specialized.

  • The specialization must live in an archive element.

  • A non-specialization of the template member must live in an object file seen by the linker. For a non-specialization to live in an object file, -ptused must have been specified (in other words, not the default mode).

  • Nothing else that exists in the archive element is referenced; that is, the specialization is probably the only thing in the object file.

You can use either of the following two workarounds:

  • Force the archive element to be loaded by defining some dummy global within it and passing the -u option to the linker to force an undefined reference to the dummy global.

  • Use a .so (that is, a dynamic shared object) instead of an archive. The runtime loader correctly selects specializations from dynamic shared objects.

Undetected Link-Time Changes in Template Implementation Files

There is no link-time mechanism to detect changes in template implementation files or to re-instantiate those template bodies that are out of date when you use the -ptused option.

Since a Makefile usually makes object files dependent on the .h files where templates are defined, make may not enable you to rebuild the right set of object files if you modify a template implementation file. To make sure you rebuild all files that instantiate a given template when the template body changes, you must follow the steps below.

  1. Use the -MDupdate option at compile time to update a dependence file (usually called Makedepend). The compiler lists dependences for all applicable #include files, including template implementation files that are implicitly included.

  2. Make sure that your Makefile includes this dependence file. See the CC(1) and make(1) man pages for more information on how to include files within a Makefile.

Prelinker Cannot Recompile Renamed Files

The only object files that the prelinker can recompile are object files that have not been renamed after they were originally compiled. In particular, the following limitations apply:

  • The prelinker cannot recompile any object file that exists in an archive, since putting an object file in an archive is equivalent to renaming it. It is recommended that you run the prelinker on object files before putting them in an archive. A similar restriction applies to dynamic shared objects. (See “Building Shared Libraries and Archives”.)

  • The prelinker cannot compile an object file if it was renamed after being compiled. For example, consider the following command line:

    yacc gram.y CC -c y.tab.c mv y.tab.o object.o

    The prelinker does not know how to recompile object.o. If object.o contains unresolved template references that are not satisfied by any other objects, you must use the -ptused option when compiling, or explicitly invoke the prelinker on the object file before moving it.

How to Transition From Cfront

If you have compiled your source code with Cfront, you may have to modify your build scripts to ensure that your templates are instantiated properly. This section discusses how to transition templates from Cfront to the Silicon Graphics environment.

Mapping Template Options From Cfront to CC

The following list contains Cfront template-related options, their meanings, and the equivalent CC options:

-pta 

Instantiates a whole template class rather than only those members that are needed. If you use automatic instantiation, there is no equivalent option for CC. If you use explicit instantiation, the -ptall option performs roughly the same action.

-pte suffix 

Uses suffix as the standard source suffix instead of .c. There is currently no equivalent CC option. CC always searches for the following suffixes when looking for a template body to implicitly include: .c, .C, .cpp, .CPP, .cxx, .CXX, .cc, .c++

-ptn 

Changes the default instantiation behavior for one-file programs to that of larger programs, where instantiation is broken out separately and the repository updated. One-file programs normally have instantiation optimized so that instantiation is done into the application object itself. There is currently no equivalent CC option.

One way of approximating this behavior is to compile your file with -c, and link it separately, instead of compiling and linking in a single step. Another method is to create an empty dummy file, and then compile and link your original file and the new dummy file in a single step. For example, you can use the following command line:

CC file.c dummy.c

-ptrpathname 

Specifies a repository, with ./ptrepository as the default. If several repositories are given, only the first is writable, and the default repository is ignored unless explicitly named. There is no equivalent option for CC. The Cfront repositories contain the following two kinds of information:

  • Information about where types and templates are defined

  • Object files containing template instantiations

The CC template instantiation mechanism does not use separate object files for template instantiations; all necessary template instantiations are performed in files that are part of the application (or library) being built. Information about which templates are capable of being instantiated by each file are embedded in the object file itself. This means that repositories are not needed. See “Using Object Files From Cfront's Repository”, and “What to Do If You Use Multiple Repositories”, for more information.

-pts 

Splits instantiations into separate object files, with one function per object (including overloaded functions), and all class static data and virtual functions grouped into a single object. There is no equivalent CC option. You can exercise fine-grained control over exactly which templates are instantiated in each file by using the instantiation #pragma directives described in “#pragma Directives for Template Instantiation”.

-ptv 

Turns on verbose or verify mode, which displays each phase of instantiation as it occurs, together with the elapsed time in seconds that phase took to complete. You should use this option if you are new to templates. Verbose mode displays the reason an instantiation is done and the exact CC command used. The -ptv option is also supported by CC and provides verbose information about the operation of the prelinker. The prelinker indicates which template instantiations are being assigned to which files and which files are being recompiled.

Using Object Files From Cfront's Repository

If you are familiar with the Cfront template instantiation mechanism, you may sometimes explicitly reference object files in the repository. This is often done when building an archive or a shared library. The general idea is to link a fake main program with a set of object files so as to populate the repository with the necessary template instantiations. The object files that were linked, along with the object files in the repository, are stored in an archive or linked into a shared library.

Users of Cfront do this to build an archive or library that has no unresolved template references. CC users who want to build archives and shared libraries where all template references have been resolved can do the following:

  • If you are building a shared library, the CC compiler will automatically run the prelinker on the set of object files being linked into the shared libraries. No further action is necessary.

  • If an archive is being built, the prelinker needs to be run explicitly on the object files, before invoking ar. See “Building Shared Libraries and Archives”, for information on this action.

What to Do If You Use Multiple Repositories

If you use the Cfront template instantiation mechanism, you may sometimes use multiple repositories. For example, you may have an application which consists of multiple libraries. Each library is built in its own directory, and has its own repository. When you build the library, template functions are not instantiated. When the application is linked against these libraries, the necessary templates are instantiated at link time. The repositories provide enough information about where to find the necessary template declarations and implementations.

CC does not use repositories, and you can use various strategies when linking a set of object files against a set of libraries that contain references to uninstantiated template functions. Some examples are given below:

  • If all uninstantiated template functions can be instantiated in the object files being linked into the application, the prelinker does so automatically. However, it is possible that a library uses a template internally, which is never used by the object files being linked into the application. Such templates are not instantiated by the prelinker, resulting in undefined symbols.

  • A better strategy is to prelink each library when it is built, so that the main program is not burdened with having to perform these instantiations. One problem occurs if multiple libraries use the same template functions: if each library is prelinked, multiple copies of such functions will be generated. Removal of duplicate functions takes place only in .o and .a files; shared libraries cannot have any duplicate code removed.

Template Language Support

The language support for templates in the Silicon Graphics C++ environment is more extensive than for Cfront. The following are some of the additional template language constructs supported by the Silicon Graphics C++ environment:

  • You may use nested classes, typedefs, and enums in class templates, including variant typedefs and enums. (A variant member type depends on the template parameters in some way.)

  • You may use floating point numbers, pointers to members, and more flexible specifications of constant addresses.

  • You may use default arguments for class template non-type parameters. For example:

    template <int I = 1> class A {};

  • You may allow a non-type template parameter to have another template parameter as its type. For example:

    template <class T, T t> class A {
    public:
    T  a;
    A(T init_val = t)  { a = init_val; }
    };

  • You may use what are essentially template classes instantiated with the template parameters of other class or function templates. For example:

    template <class T, int I> struct A {
    static T b[I];
    };
    
    template <class T> void f(A<T,10> x) {}
    template <class T> void f(A<T, 3> x) {}
    
    void main() {
    A<int,10> m;
    A<int,3> n;
    int i = f(m);
    int j = f(n);
    }

    The function template would be considered tagged twice by Cfront. The code calls would be tagged ambiguous by the Borland C++ compiler.

  • You may use circular template references. For example:

    template <class T> class B;
    template <class T> class C;
    
    template <class T> class A { B<T> *b; };
    template <class T> class B { C<T> *c; };
    template <class T> class C { A<T> *a; };
    
    A<int> a;

    This code causes Cfront to generate an error.

  • You may use forward declarations of class specializations.

  • You may use nested classes as type arguments of class templates.

  • You may use default arguments for all types of function templates, including arguments based on template parameter types. For example:

    template <class T> void f(T t, int i = 1) {}
    template <class T> void f(T t, T i = 1) {}

  • CC is more consistent than other C++ compilers about where a class template name must be followed by template arguments. For example:

    template <class T> struct X {
    X();
    ~X();
    X*x;
    int X::* x2;
    void f();
    void g(){ X x;}
    };
    
    struct X<char> {
    X();
     ~X();                             // Borland error
    X*x;                               // Borland error
    int X::* x2;                       // Borland error
    void f();
    void g(){ X x;}                    // Borland error };
    
    template <class T> void X<T>::f(){
    X x;                               // cfront error }
    
    void X<char>::f() {
    X x;                               // cfront & Borland error
    }
    
    X<int> x;
    X<char> xc;

    Cfront allows X to be used as a type name in the inline body of g but not in the out-of-line body of f. Borland/C++ uses one set of rules for class templates and a different set of rules for specializations. With CC, you may use X in all of the cases shown.