Dependent names

From cppreference.com
< cpp‎ | language
 
 
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements
Jump statements
Functions
function declaration
lambda function declaration
function template
inline specifier
exception specifications (deprecated)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
decltype specifier (C++11)
Specifiers
cv specifiers
storage duration specifiers
constexpr specifier (C++11)
auto specifier (C++11)
alignas specifier (C++11)
Initialization
Literals
Expressions
alternative representations
Utilities
Types
typedef declaration
type alias declaration (C++11)
attributes (C++11)
Casts
implicit conversions
const_cast conversion
static_cast conversion
dynamic_cast conversion
reinterpret_cast conversion
C-style and functional cast
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
class template
function template
template specialization
parameter packs (C++11)
Miscellaneous
Inline assembly
 

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 the template parameter.

Contents

[edit] Dependent types

The following types are dependent types:

  • template parameter
  • a member of an unknown specialization
  • a nested class or enum that is a member of unknown specialization
  • 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 expression
  • template instantiation whose name is a template parameter, or any of template arguments is a dependent type/expression
  • the result of decltype() applied to a type-dependent expression

[edit] Type-dependent expressions

The following expressions are type-dependent

  • an expression whose any subexpression is a type-dependent expression
  • an expression that contains an identifier whose type is a dependent type
  • an expression that contains a dependent template instantiation
  • an expression that contains a conversion function call to a dependent type
  • an expression that contains a nested name that refers to a member of unknown specialization
  • any cast expression to a dependent type
  • new-expression that creates an object of a dependent type
  • an expression that names a static data member of type array of unknown bound
  • this, if the class type is a dependent type
  • member access expression that refers to a member of unknown specialization
  • member access expression that refers to a member of the current instantiation, whose type is a dependent type

[edit] Value-dependent expressions

  • an expression whose any subexpression is a value-dependent expression
  • an expression that contains the name of a non-type template parameter
  • an expression that contains a name declared with a dependent type
  • a constant initialized from a value-dependent expression
  • sizeof, alignof, or typeid of a type-dependent expression or of a dependent type
  • noexcept of a type-dependent expression
  • any cast expression to a dependent type or from a value-dependent expression
  • an expression that names a member of unknown specialization

[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

Dependent names are looked up in both the declaration and the instantiation context.

If a function call is made that depends on a template parameter, the usual name lookup is modified as follows:

  • Only the function declarations visible from template definition are considered by unqualified/qualified (whichever applicable) name lookup
  • Only the function declarations visible from template definition and from the template instantiation are considered by argument-dependent lookup
#include <string>
#include <iostream>
 
// visible at the point of definition of f1,
// but not reachable by normal lookup because of X::g1
void g1(std::string) { std::cout << "::g1(string)\n"; }
// visible at the point of definition of f2,
void g2(std::string) { std::cout << "::g2(string)\n"; }
 
namespace X {
  // found by normal lookup from the point of definitoin
  // also prevents normal lookup from leaving namespace X
  void g1(int) { std::cout << "X::g1(int)\n"; };
  // point of definition of f1
  template < class T > void f1(T t) { g1(t); }
  // point of definition of f2
  template < class T > void f2(T t) { g2(t); }
}
 
// not visible at the point of definition
void g1(char) { std::cout << "::g1(char)\n"; }
void g2(bool) { std::cout << "::g2(bool)\n"; }
 
namespace N {
  struct Z {};
  // visible only if the namespace N is examined
  void g1(Z) { std::cout << "N::g1(Z)\n"; }
}
 
// not visible
void g1(N::Z) { std::cout << "::g1(N::Z)'\n"; }
 
int main() {
 
  int i;
  char c;
  double d;
  std::string s;  
  N::Z z;
 
  X::f1(i); // finds X::g1(int) by normal lookup from definition
  X::f1(c); // finds X::g1(int), not ::g1(char)
//  X::f1(s); // error: ::f1(string) cannot be found either by
              // normal lookup from the point of definition,
              // or by ADL (which considers the std namespace in this case)
  X::f1(z); // finds N::g1(Z) via ADL, not ::g1(Z)
  X::f2(s); // finds ::g2(string) from the point of definition
//  X::f2(false); // error: ::g2(bool) cannot be found by normal lookup
                 // from the point of definitino of X::f2
 
}

Output:

X::g1(int)
X::g1(int)
N::g1(Z)
::g2(string)

The purpose of this is rule is to help guard against violations of the one-definition-rule for template instantiations. For instance:

// 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 someExternalLibrary::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, were it not for the argument-dependent name lookup rule, the instantiation of E::writeObject<vector<int>> would have two different definitions: one using P1::operator<< and one using P2::operator<<. On many implementations this may very well not be detected, 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:

// an external libary
 
namespace E {
  template<typename T>
  void writeObject(const T& t) {
    std::cout << "Value = " << t << '\n';
  }
}
 
// translation unit 1:
 
namespace P1 {
  // user-defined type in P1 namespace
  struct my_int
  {
     int m;
     my_int() : m() {}
     my_int(int p) : m(p) {}
     operator int() const { return m; }
  };
  std::ostream& operator<<(std::ostream& os, const std::vector<my_int>& v) {
      for(int n: v) os << n; return os;
  }
  void doSomething() {
    vector<my_int> v;
    writeObject(v); // OK: instantiates writeObject(std::vector<P1::my_int>)
                    //     which finds P1::operator<< via ADL
  }
}


Note: this rule prevents code such as

#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 obejct 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 template definition, 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.

#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; 
}
 
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.

[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;