Dependent names

From cppreference.com
< cpp‎ | language

Inside the definition of a template (both class template and function template), the contents of some types and the values and the types of some expressions are not known until the template is instantiated, because they depend on template parameters.

Contents

[edit] Dependent types

The following types are dependent types:

  • template parameter
  • a member of an unknown specialization (see below)
  • a nested class/enum that is a dependent member of unknown specialization (see below)
  • a cv-qualified version of a dependent type
  • a compound type constructed from a dependent type
  • an array type constructed from a dependent type, or whose size is a value-dependent constant expression (see below)
  • a template-id where either the template name is a template parameter, or any of template arguments is type- or value-dependent
  • the result of decltype applied to a type-dependent expression

Note: a typedef member of a current instantiation is only dependent the type it refers to is.

[edit] Type-dependent expressions

The following expressions are type-dependent

  • an expression whose any subexpression is a type-dependent expression
  • this, if the class is a dependent type.
  • an id-expression that
  • contains an identifier for which name lookup finds at least one dependent declaration
  • contains a dependent template-id
  • contains the name of conversion function to a dependent type
  • contains a nested name specifier or qualified-id that is a member of unknown specialization
  • names a dependent member of the current instantiation which is a static data member of type "array of unknown bound"
  • any cast expression to a dependent type
  • new-expression that creates an object of a dependent type
  • member access expression that refers to a member of the current instantiation whose type is dependent
  • member access expression that refers to a member of unknown specialization

Note: literals, pseudo-destructor calls, sizeof, alignof, typeid, delete-expressions, throw-expressions, and noexcept-expressions are never type-dependent because the types of these expressions cannot be.

[edit] Value-dependent expressions

  • is a name declared with a dependent type
  • is a name of a non-type template parameter
  • names a member of unknown specialization
  • names a static data member that is a dependent member of the current instantiation and is not initialized.
  • names a static member function that is a depndent member of the current instantiation
(since C++14)
  • is a constant with a literal type, initialized from a value-dependent expression
  • sizeof, alignof, typeid, noexcept-expressions where the argument is a type-dependent expression or a dependent type-id
  • any cast expression to a dependent type or from a value-dependent expression
  • address-of expression where the argument is qualified-id that names a dependent member of the current instantiation
(since C++14)

[edit] Binding rules

Non-dependent names are looked up and bound at the point of template definition. This binding holds even if at the point of template instantiation there is a better match:

#include <iostream>
void g(double) { std::cout << "g(double)\n"; }
 
template<class T>
struct S {
    void f() const {
        g(1); // non-dependent expression, bound now
    }
};
 
void g(int) { std::cout << "g(int)\n"; }
 
int main()
{
    g(1); // calls g(int)
 
    S<int> s;
    s.f(); // calls g(double)
}


[edit] Lookup rules

As discussed in lookup, the lookup of a dependent name used in a template is postponed until the template arguments are known, at which time

  • non-ADL lookup examines function declarations with external linkage that are visible from the template definition context
  • ADL examines function declarations with external linkage that are visible from both the template definition context and the template instantiation context

(in other words, adding a new function declaration after template definition does not make it visible, except via ADL).

The purpose of this is rule is to help guard against violations of the ODR for template instantiations:

// an external libary
namespace E {
  template<typename T>
  void writeObject(const T& t) {
    std::cout << "Value = " << t << '\n';
  }
}
 
// translation unit 1:
// Programmer 1 wants to allow E::writeObject to work with vector<int>
namespace P1 {
  std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) {
      for(int n: v) os << n; return os;
  }
  void doSomething() {
    std::vector<int> v;
    E::writeObject(v); // error: will not find P1::operator<<
  }
}
 
// translation unit 2:
// Programmer 2 wants to allow E::writeObject to work with vector<int>
namespace P2 {
  std::ostream& operator<<(std::ostream& os, const std::vector<int>& v) {
      for(int n: v) os << n; return os;
  }
  void doSomethingElse() {
    std::vector<int> v;
    E::writeObject(v); // error: will not find P2::operator<<
  }
}

In the above example, if non-ADL lookup for operator<< were allowed from the instantiation context, the instantiation of E::writeObject<vector<int>> would have two different definitions: one using P1::operator<< and one using P2::operator<<. Such ODR violation may not be detected by the linker, leading to one or the other being used in both instances.

To make argument-dependent lookup examine a user-defined namespace, either std::vector should be replaced by a user-defined class or its element type should be a user-defined class:

namespace P1 {
  // if C is a class defined in the P1 namespace
  std::ostream& operator<<(std::ostream& os, const std::vector<C>& v) {
      for(C n: v) os << n; return os;
  }
  void doSomething() {
    std::vector<C> v;
    E::writeObject(v); // OK: instantiates writeObject(std::vector<P1::C>)
                       //     which finds P1::operator<< via ADL
  }
}

Note: this rule makes it impractical to overload operators for standard library types

#include <iostream>
#include <vector>
#include <iterator>
#include <utility>
 
// Bad idea: operator in global namespace, but its arguments are in std::
std::ostream& operator<<(std::ostream& os, std::pair<int, double> p)
{
    return os << p.first << ',' << p.second;
}
 
int main()
{
    typedef std::pair<int, double> elem_t;
    std::vector<elem_t> v(10);
    std::cout << v[0] << '\n'; // OK, ordinary lookup finds ::operator<<
    std::copy(v.begin(), v.end(),
              std::ostream_iterator<elem_t>(std::cout, " ")); // Error: both ordinary 
    // lookup from the point of definition of std::ostream_iterator and ADL will 
    // only consider the std namespace, and will find many overloads of
    // std::operator<<, so the lookup will be done. Overload resolution will then
    // fail to find operator<< for elem_t in the set found by the lookup.
}


[edit] Current instantiation

Within the definition of a template class or nested class of a template class (or member function thereof), some names will be immediately bound to members of that class.

In such a context, the term current instantiation simply refers to the instantiation of that class template with its given parameters as arguments (i.e. the class or member that is actually being defined):

template < typename T1, typename T2 > struct C {
 
  C *p1;                // C is the current instantiation
  C< T1, T2 > *p2;      // C< T1, T2 > is the current instantiation
  C< T1 *, T2 * > *p3;  // C< T1 *, T2 * > is not the current instantiation
  C< T2, T1 > *p4;      // C< T2, T1 > is not the current instantiation
 
  struct D {
    D *q0;                  // D is the current instantiation
    C::D *q1;               // C::D is the current instantiation
    C< T1, T2 >::D *q2;     // C< T1, T2 >::D is the current instantiation
    C< T1 *, T2 * >::D *q3; // C< T1 *, T2 * >::D is not the current instantiation
    C< T2, T1 >::D *q4;     // C< T2, T1 >::D is not the current instantiation
  };
};
 
template < typename T1, typename T2 > struct C< T1 *, T2 * > {
  C *p1;                // C is the current instantiation
  C< T1 *, T2 * > *p2;  // C< T1 *, T2 * > is the current instantiation
  C< T1, T2 > *p3;      // C< T1, T2 > is not the current instantiation
};


Specifically, the current instantiation is referenced within a class template by one of:

  • the template name [Ex: C in both templates above]
  • the template name with its formal parameters as arguments [Ex: C< T1, T2 > within the primary template above, C< T1 *, T2 * > within the partial specialization above]

Or, within a nested class of a class template:

  • the nested class name referenced as a member of the current instantiation [Ex: D, C::D, C< T1, T2 >::D above].

A name used within a template class or class member, though dependent, may be understood to reference a member of the current instantiation:

struct Z { int z; };
template < typename T > struct Y { int y; };
template < typename T > struct A: public B< T >, public Z {
  int i;
  int getI() const { return i; }            // refers to i declared above
  int getI2() const { return A::i; }        // refers to i declared above
  int getI3() const { return A< T >::i; }   // refers to i declared above
  int getJ() const { return A< T >::j; }    // could perhaps be B< T >::j
  int getK() const { return A< T * >::k; }  // could perhaps be B< T >::k
                                            //  or a member k of a 
                                            //  partial or explicit 
                                            //  specializaton of A
  int getZ() const { return z; }            // refers to Z::z
  int getY() const { return Y< T >::y; }    // refers to Y< T >::y	      
 
  static int getI(A *a) { return a->i; }        // refers to A::i in (*a)
  static int getJ(A *a) { return a->j; }        // could perhaps be B< T >::j in (*a)
  static int getK(A< T > *a) { return a->k; }   // could perhaps be B< T >::k in (*a)
                                                //  or a member k of a partial or 
                                                //  explicit specializaton of A in (*a)
  static int getZ(A *a) const { return a->z; }  // refers to Z::z in (*a)	
};


A name is a member of the current instantiation if it is either:

  • an unqualified class member name [Ex: i above]
  • an unqualified member name of a non-dependent base class [Ex: z above]
  • a qualified class member name, where the class name is the current instantiation [Ex: A::i or A< T >::i above]
  • an object member reference, where the object type is the current instantiation [Ex: a->i above]

Note that it is implicit in these requirements that name lookup finds a declaration of the name within the scope of the class.

[edit] Unknown specializations

Within a template class or function definition, lookup of a qualified name or object member reference may be delayed until instantiation if it is deemed to be a member of an unknown specialization (i.e. belonging to a template specialization whose definition is not yet known). Such a specialization could be:

  • a dependent base class
  • a partial or explicit specialization of the given template
  • a specialization of an entirely different template

Referring to the second example in the section on Current instantiation, within a class scope, a qualified name or object member reference is a member of an unknown specialization if the qualifying class or object type (call it QT) is dependent and either:

  • QT is not the current instantiation [Ex: A< T * >::k, Y< T >::y, a->k above]
  • QT is the current instantiation, but does not contain a declaration of the name and has at least one dependent base [Ex: A< T >::j, a->j above]

The latter case implies that when the qualified name or object type is the current instantiation, then either the name must be declared within the class or the class must have a dependent base.

[Note: Within a non-member function template, all such names with dependent qualifying/object types are considered to be members of unknown specializations, since obviously there are no members to speak of in such a scope.]

An attempt to refer to a name whose qualifier is the current instantiation, but which is neither a member of current instantiation nor a member of unknown specialization can be reported as an error immediately:

template<typename T> struct Base {};
 
template<typename T>
struct Derived : Base<T> {
    void f() {
        // unknown_type is a member of unknown specialization of Base<T>
        // lookup deferred
        typename Derived<T>::unknown_type z;
    }
};
 
template<> struct Base<int> { // this specialization provides it
    typedef int unknown_type; 
};
 
template<typename T>
struct A {
   void f() {
       // error_type is not a member of unknown specialization
       // (there is no base that could define it)
       // and not a member of current instantiation either
       // Error (may be) detected immediately
       typename A<T>::error_type y;
   }
};

[edit] The typename disambiguator for dependent names

In a declaration or a definition of a template, including alias template, a name that is not a member of the current instantiation and is dependent on a template parameter is not considered to be a type unless the keyword typename is used or unless it was already established as a type name, e.g. with a typedef declaration or by being used to name a base class.

#include <iostream>
#include <vector>
 
int p = 1;
template <typename T>
void foo (const std::vector<T> &v)
{
 
    // std::vector<T>::const_iterator is a dependent name,
    typename std::vector<T>::const_iterator it = v.begin();
 
    // without 'typename', the following is parsed as multiplication 
    // of the type-dependent member variable 'const_iterator' 
    // and some variable 'p'. Since there is a global 'p' visible
    // at this point, this template definition compiles.
    std::vector<T>::const_iterator* p; 
 
    typedef typename std::vector<T>::const_iterator iter_t;
    iter_t * p2; // iter_t is a dependent name, but it's known to be a type name
}
 
template<typename T>
struct S {
    typedef int value_t; // member of current instantiation
    void f() {
        S<T>::value_t n{};  // S<T> is dependenent, but 'typename' not needed
        std::cout << n << '\n';
    }
};
 
int main()
{
    std::vector<int> v;
    foo(v); // template instantiation fails: there is no member variable
            // called 'const_iterator' in the type std::vector<int>
    S<int>().f();
}


The keyword typename may only be used in this way before qualified names, e.g. T::x.

Usual qualified name lookup is used for the identifier prefixed by typename, the rules do not change despite the qualifier:

struct A { // A has a nested variable X and a nested type struct X
   struct X {};
   int X;
};
struct B {
    struct X { }; // B has a nested type struct X
};
template<class T> void f(T t) {
    typename T::X x;
}
void foo() {
    A a;
    B b;
    f(b); // OK: instantiates f<B>, T::X refers to B::X
    f(a); // error: cannot instantiate f<A>:
          // because qualified name lookup for A::X finds the data member
}

[edit] The template disambiguator for dependent names

Similarly, in a template definition, a dependent name that is not a member of the current instantiation is not considered to be a template name unless the disambiguation keyword template is used:

template<typename T>
struct S {
    template<typename U> void foo(){}
};
 
template<typename T>
void bar()
{
    S<T> s;
    s.foo<T>(); // error: < parsed as less than operator
    s.template foo<T>(); // OK
}


The keyword template may only be used in this way after operators :: (scope resolution), -> (member access through pointer), and . (member access), the following are all valid examples:

  • T::template foo<X>();
  • s.template foo<X>();
  • this->template foo<X>();
  • typename T::template iterator<int>::value_type v;

Due to the special rules for unqualified name lookup for template names in member access expressions, when a template name appears in a member access expression (after -> or after .), the disambiguator is unnecessary if there is a template with the same name found by ordinary lookup in the context of the expression. However, if the template found by lookup in the context of the expression differs from the one found in the context of the class, the program is ill-formed (until C++11)

#include <set>
using std::set; // makes 'set' visible to lookup from bar
 
template<typename T> 
struct S { 
    template<typename U> void set(){}
};
 
template<typename T>
void bar()
{
    S<T> s;
    s.set<T>(); // not an error if ::set is visible:
                // (and since C++11, this is well-formed)
    s.template set<T>(); // works with and without ::set
}