This section describes problems you might encounter when using templates.
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 mean the same thing 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.
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 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.
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.
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 there is no error message during compilation because the compiler reads the following 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 ahead of 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:
#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<<(std::ostream&, const array<T>&); }; #endif
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 is true even if the compiler can "know" 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
Because the ">>" character sequence is interpreted as the right-shift operator, you must be careful when you use one template declaration 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
Array<String<10 >> short_string_array(100);
Array<String<10> > short_string_array(100);