7.8 Name LookupTemplates introduce a new wrinkle to name lookup. (See Chapter 2 for the non-template rules of name lookup.) When compiling a template, the compiler distinguishes between names that depend on the template parameters (called dependent names) and those that do not (nondependent names). Nondependent names are looked up normally when the template is declared. Dependent names, on the other hand, must wait until the template is instantiated, when the template arguments are bound to the parameters. Only then can the compiler know what those names truly mean. This is sometimes known as two-phase lookup. 7.8.1 Dependent NamesThis section describes dependent names, and the following section describes what the compiler does with them. A dependent name can have different meanings in different template instantiations. In particular, a function is dependent if any of its arguments are type-dependent. An operator has a dependent name if any of its operands are type-dependent. A dependent type is a type that that can change meaning if a template parameter changes. The following are dependent types:
A type-dependent expression has a dependent type. It can be any of the following:
A constant expression can also be value-dependent if its type is dependent or if any subexpression is value-dependent. An identifier is value-dependent in the following cases:
A sizeof expression is value-dependent only if its operand is type-dependent. A cast expression is dependent if its operand is a dependent expression. Example 7-11 shows a variety of dependent types and expressions. Example 7-11. Dependent namestemplate<typename T> struct base { typedef T value_type; // value_type is dependent. void func(T*); // func is dependent. void proc(int); // proc is nondependent. class inner { // inner is dependent. int x; // x is nondependent. }; template<unsigned N> class data { // data is dependent. int array[N]; // array is dependent. }; class demo : inner { // demo is dependent. char y[sizeof(T)]; // y is dependent. }; }; int main( ) { base<int> b; } 7.8.2 Resolving NamesWhen writing a template declaration or definition, you should use qualified names as much as possible. Use member expressions to refer to data members and member functions (e.g., this->data). If a name is a bare identifier, the name lookup rules are different from the rules for non-templates. The compiler looks up unqualified, nondependent names at the point where the template is declared or defined. Dependent base classes are not searched (because, at the point of declaration, the compiler does not know anything about the instantiation base class). This can give rise to surprising results. In the following example, the get_x member function does not see base<T>::x, so it returns the global x instead: template<typename T> struct base { double x; }; int x; template<typename T> struct derived : base<T> { int get_x( ) const { return x; } // Returns ::x }; Dependent names are looked up twice: first in the context of the declaration and later in the context of the instantiation. In particular, when performing argument-dependent name lookup (Chapter 2), the compiler searches the declaration and instantiation namespaces for the function argument types. Essentially, the instantiation context is the innermost namespace scope that encloses the template instantiation. For example, a template instance at global scope has the global scope as its instantiation context. The context for an instance that is local to a function is the namespace where the function is defined. Thus, the instantiation context never includes local declarations, so dependent names are never looked up in the local scope. A function template can have multiple instantiation points for the same template arguments in a single source file. A class template can have multiple instantiations for the same template arguments in multiple source files. If the different contexts for the different instantiations result in different definitions of the templates for the same template arguments, the behavior is undefined. The best way to avoid this undefined behavior is to avoid using unqualified dependent names. Example 7-12 shows several ways dependent name lookup affects a template. In particular, note that iterator_base can refer to its members without qualification or member expressions. However, the derived classes, such as iterator, must use this-> or qualify the member name with the base class because the base class is not searched for unqualified names. The print member function is also interesting. It prints the array by using an ostream_iterator, which calls operator<< to print each element. The name operator<< is dependent, so it is not looked up when the template is declared, but when the template is instantiated. At that time, the compiler knows the template argument, big::integer, so it also knows to search the big namespace for the right overloaded operator<<. Example 7-12. Resolving dependent names#include <algorithm> #include <iostream> #include <iterator> #include <ostream> #include <stdexcept> template<unsigned Size, typename T> class array { template<unsigned Sz, typename U> friend class array<Sz,U>::iterator; template<unsigned Sz, typename U> friend class array<Sz,U>::const_iterator; class iterator_base { public: iterator_base& operator++( ) { ++ptr; return *this; } T operator*( ) const { check( ); return *ptr; } protected: iterator_base(T* s, T* p) : start(s), ptr(p) {} void check( ) const { if (ptr >= start + Size) throw std::out_of_range("iterator out of range"); } T* ptr; T* start; }; public: array( ): data(new T[Size]) {} class iterator : public iterator_base, public std::iterator<std::random_access_iterator_tag,T> { public: iterator(T* s, T* p) : iterator_base(s, p) {} operator const_iterator( ) const { return const_iterator(this->start, this->ptr); } T& operator*( ) { iterator_base::check( ); return *this->ptr; } }; iterator begin( ) { return iterator(data, data); } iterator end( ) { return iterator(data, data + Size); } template<typename charT, typename traits> void print(std::basic_ostream<charT,traits>& out) const { std::copy(begin( ), end( ), std::ostream_iterator<T>(out)); } private: T* data; }; namespace big { class integer { public: integer(int x = 0) : x_(x) {} operator int( ) const { return x_; } private: int x_; // Actual big integer implementation is left as an exercise. }; template<typename charT, typename traits> std::basic_ostream<charT,traits>& operator<<(std::basic_ostream<charT,traits>& out, const integer& i) { out << int(i); return out; } } int main( ) { const array<10, big::integer> a; a.print(std::cout); } 7.8.3 Hiding NamesWhen using templates, several situations can arise in which template parameters hide names that would be visible in a non-template class or function. Other situations arise in which template parameter names are hidden by other names. If a member of a class template is defined outside of the namespace declaration that contains the class template, a template parameter name hides members of the namespace: namespace ns { template<typename T> struct demo { demo( ); T x; }; int z; } template<typename z> ns::demo::demo( ) { x = z( ); // Template parameter z, not ns::z } Template parameter names are hidden in the following cases:
If a base class of a class template is a dependent type, it and its members do not hide template parameters, and the base class is not searched when unqualified names are looked up in the derived-class template. 7.8.4 Using Type NamesWhen parsing a template definition, the compiler must know which names are types and which are objects or functions. Unqualified names are resolved using the normal name lookup rules (described earlier in this section). Qualified dependent names are resolved according to a simple rule: if the name is prefaced with typename, it is a type; otherwise, it is not a type. Use typename only in template declarations and definitions, and only with qualified names. Although typename is meant to be used with dependent names, you can use it with nondependent names. Example 7-13 shows a typical use of typename. Example 7-13. Using typename in a template definition// Erase all items from an associative container for which a predicate returns // true. template<typename C, typename Pred> void erase_if(C& c, Pred pred) { // Need typename because iterator is qualified for (typename C::iterator it = c.begin( ); it != c.end( );) if (pred(*it)) c.erase(*it++); else ++it; } |