DekGenius.com
Team LiB   Previous Section   Next Section

7.8 Name Lookup

Templates 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 Names

This 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:

  • The name of a type template parameter or template template parameter:

    template<typename T> struct demo { T dependent; }
  • A template instance with template arguments that are dependent:

    template<typename T> class templ {};
    template<typename U> class demo { templ<U> dependent; }
  • A nested class in a dependent class template (such as the class template that contains the nested class):

    template<typename T> class demo { class dependent {}; }
  • An array that has a base type that is a dependent type:

    template<typename T> class demo { T dep[1]; }
  • An array with a size that is value-dependent (defined later in this section):

    template<typename T> class demo { int dep[sizeof(T)]; }
  • Pointers and references to dependent types or functions (that is, functions whose return types or parameter types are dependent or whose default arguments are dependent):

    template<typename T> class demo { T& x; T (*func)(  ); }
  • A class, struct, or union that depends on a template parameter for a base class or member (note that a non-template class nested in a class template is always dependent):

    template<typename T> class demo { class nested { T x; }; };
  • A const- or volatile-qualified version of a dependent type:

    template<typename T> class demo { const T x; };
  • A qualified name, in which any qualifier is the name of a dependent type:

    template<typename T> struct outer { struct inner {}; };
    template<typename T> class demo { outer<T>::inner dep; };

A type-dependent expression has a dependent type. It can be any of the following:

  • this, if the class type is dependent

  • A qualified or unqualified name if its type is dependent

  • A cast to a dependent type

  • A new expression, creating an object of dependent type

  • Any expression that is built from at least one dependent subexpression, except when the result type is not dependent, as in the following:

    • A sizeof or typeid expression

    • A member reference (with the . or -> operators)

    • A throw expression

    • A delete expression

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:

  • When it is the name of an object with a dependent type

  • When it is the name of a value template parameter

  • When it is a constant of integral or enumerated type, and its initial value is a value-dependent expression

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 names
template<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 Names

When 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 Names

When 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 member is defined outside of its class template, members of the class or class template hide template parameter names:

    template<typename T>
    struct demo {
      T x;
      demo(  );
    };
    template<typename x>
    demo::demo(  ) { x = 10; } // Member x, not parameter x
  • In the definition of a member of a class template that lies outside the class definition, or in the definition of a class template, a base class name or the name of a member of a base class hides a template parameter if the base class is nondependent:

    struct base {
      typedef std::size_t SZ;
    };
    template<int SZ>
    struct derived : base {
      SZ size; // base::SZ hides template parameter.
    };

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 Names

When 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;
}
    Team LiB   Previous Section   Next Section