Go to main content
Oracle® Developer Studio 12.5: C++ User's Guide

Exit Print View

Updated: July 2016
 
 

6.7 Template Problem Areas

This section describes problems you might encounter when using templates.

6.7.1 Nonlocal Name Resolution and Instantiation

Sometimes a template definition uses names that are not 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. A name can have different meanings in different places, yielding different resolutions.

Name resolution is complex. Consequently, you should not rely on nonlocal names except those provided in a pervasive global environment. That is, use only nonlocal names that are declared and defined the same way everywhere. In the following example, the template function converter uses the nonlocal names intermediary and temporary. These names have different definitions in use1.cc and use2.cc, and will probably yield different results under different compilers. For templates to work reliably, all nonlocal names (intermediary and temporary in this case) must have the same definition everywhere.

use_common.h
// Common template definition
template <class Source, class Target>
Target converter(Source source)
       {temporary = (intermediary)source;
       return (Target)temporary;}
use1.cc
typedef int intermediary;
int temporary;

#include "use_common.h"
use2.cc
typedef double intermediary;
unsigned int temporary;

#include "use_common.h"

A common use of nonlocal 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.

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

Example 1  Example of Local Type as Template Argument Problem
array.h
template <class Type> class Array {
        Type* data;
        int   size;
    public:
        Array(int sz);
        int GetSize();
};

array.cc
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(10);

file2.cc
#include "array.h"
struct Foo {double data;};
Array<Foo> File2Data(20);

The Foo type 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.

6.7.3 Friend Declarations of Template Functions

Templates must be declared before they are used. A friend declaration constitutes a use of the template, not a declaration of the template. A true template declaration must precede the friend declaration. For example, when the compilation system attempts to link the produced object file for the following example, it generates an undefined error for the operator<< function, which is not instantiated.

Example 2  Example of Friend Declaration Problem
array.h
// generates undefined error for the operator<< function
#ifndef ARRAY_H
#define ARRAY_H
#include <iosfwd>

template<class T> class array {
    int size;
public:
    array();
    friend std::ostream&
        operator<<(std::ostream&, const array<T>&);
};
#endif

array.cc
#include <stdlib.h>
#include <iostream>

template<class T> array<T>::array() {size = 1024;}

template<class T>
std::ostream&
operator<<(std::ostream& out, const array<T>& rhs)
    {return out <<’[’ << rhs.size <<’]’;}

main.cc
#include <iostream>
#include "array.h"

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

Note that no error message is issued during compilation because the compiler reads the following line as the declaration of a normal function that is a friend of the array class.

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

Because operator<< is really a template function, you need to supply a template declaration for it prior to the declaration of template class array. However, because operator<< has a parameter of type array<T>, you must precede the function declaration with a declaration of array<T>. The file array.h must look like this example:

#ifndef ARRAY_H
#define ARRAY_H
#include <iosfwd>

// the next two lines declare operator<< as a template function
template<class T> class array;
template<class T>
    std::ostream& operator<<(std::ostream&, const array<T>&);

template<class T> class array {
    int size;
public:
    array();
    friend std::ostream&
      operator<< <T> (std::ostream&, const array<T>&);
};
#endif

6.7.4 Using Qualified Names Within Template Definitions

The C++ standard requires types with qualified names that depend upon template arguments to be explicitly noted as type names with the typename keyword. This requirement applies even if the compiler can deduce that it should be a type. The comments in the following example show the types with qualified names that require the typename keyword.

struct simple {
  typedef int a_type;
  static int a_datum;
};
int simple::a_datum = 0; // not a type
template <class T> struct parametric {
  typedef T a_type;
  static T a_datum;
};
template <class T> T parametric<T>::a_datum = 0;   // not a type
template <class T> struct example {
  static typename T::a_type variable1;             // dependent
  static typename parametric<T>::a_type variable2; // dependent
  static simple::a_type variable3;                 // not dependent
};
template <class T> typename T::a_type             // dependent
  example<T>::variable1 = 0;                      // not a type
template <class T> typename parametric<T>::a_type // dependent
  example<T>::variable2 = 0;                      // not a type
template <class T> simple::a_type   // not dependent
example<T>::variable3 = 0;          // not a type

6.7.5 Nesting Template Names

Because the “>>” character sequence is interpreted as the right-shift operator, you must be careful when you use one template name inside another. Make sure you separate adjacent “>” characters with at least one blank space.

For example, the following ill-formed statement:

Array<String<10>> short_string_array(100); // >> = right-shift

is interpreted as:

Array<String<10 >> short_string_array(100);

The correct syntax is:

Array<String<10> > short_string_array(100);

6.7.6 Referencing Static Variables and Static Functions

Within a template definition, the compiler does not support referencing an object or function that is declared static at global scope or in a namespace. If multiple instances are generated, the One-Definition Rule (C++ standard section 3.2) is violated because each instance refers to a different object. The usual failure indication is missing symbols at link time.

If you want a single object to be shared by all template instantiations, then make the object a nonstatic member of a named namespace. If you want a different object for each instantiation of a template class, then make the object a static member of the template class. If you want a different object for each instantiation of a template function, then make the object local to the function.

6.7.7 Building Multiple Programs Using Templates in the Same Directory

If you do not use the option –instances=extern, there are no restrictions on building multiple programs in the same directory.

If you are building more than one program or library by specifying -instances=extern, build them in separate directories. If you want to build in the same directory, clean the repository between the different builds. This practice avoids any unpredictable errors. For more information, see Sharing Template Repositories.

Consider the following example with makefiles a.cc, b.cc, x.h, and x.cc. Note that this example is meaningful only if you specify -instances=extern:

........
Makefile
........
CCC = CC

all: a b

a:
    $(CCC) -I. -instances=extern -c a.cc
    $(CCC) -instances=extern -o a a.o

b:
    $(CCC) -I. -instances=extern -c b.cc
    $(CCC) -instances=extern -o b b.o

clean:
    /bin/rm -rf SunWS_cache *.o a b
...
x.h
...
template <class T> class X {
public:
  int open();
  int create();
  static int variable;
};
...
x.cc
...
template <class T> int X<T>::create() {
  return variable;
}

template <class T> int X<T>::open() {
  return variable;
}

template <class T> int X<T>::variable = 1;
...
a.cc
...
#include "x.h"

int main()
{
  X<int> temp1;

  temp1.open();
  temp1.create();
}
...
b.cc
...
#include "x.h"

int main()
{
  X<int> temp1;

  temp1.create();
}

If you build both a and b, add a make clean command between the two builds. The following commands result in an error:

example% make a
example% make b

The following commands will not produce any error:

example% make a
example% make clean
example% make b