Namespaces
Variants
Views
Actions

Dependent names

From cppreference.com
< cpp‎ | language
Revision as of 15:38, 26 April 2013 by Ericmiene (Talk | contribs)

 
 
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

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

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

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

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)
}


Dependent names are looked up in both the declaration and the instantiation context. However, for unqualified function names in the instantiation context, only argument-dependent lookup is used:

namespace M {
  void g(int);
  template < class T > void f(T t) {
    g(t);
  }
}
 
void g(char);
void g(std::string);
 
namespace N {
  struct Z {};
  void g(Z);
}
 
void g(Z);
 
int main() {
 
  int i;
  char c;
  std::string s;  
  Z z;
 
  f(i); // finds M::g(int)
  f(c); // finds M::g(int), not ::g(char)
  f(s); // error: no declaration visible to f
  f(z); // finds N::g(Z) in associated namespace of Z, not ::g(Z)
}


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

// an external libary
 
namespace E {
  template < class T > void writeObject(T t) {
    std::cout << "Value = " << t << std::endl;
  }
}
 
// translation unit 1:
 
// Programmer 1 wants to allow E::writeObject 
// to work with vector< int >
 
namespace P1 {
  std::ostream &operator<<(const vector< int > &v) {}
  void doSomething() {
    vector< int > v;
    // ...
    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<<(const vector< int > &v) {}
  void doSomethingElse() {
    vector< int > v;
    // ...
    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.

The std::vector< int > class does not have a standard output operator. There is no context-independent interpretation for std::ostream << std::vector, and this is by intent. Otherwise, the designers of std::vector would have provided one in the std namespace. Since they didn't, users should not attempt to provide one by declaring it in std - doing so will lead to multiple violations of the ODR. A "correct" solution would involve creating or deriving one's own type:

// an external libary
 
namespace E {
  template < class T > void writeObject(T t) {
    std::cout << "Value = " << t << std::endl;
  }
}
 
// translation unit 1:
 
namespace P1 {
  // ok, separate type
  class vector: public std::vector {
    using std::vector::vector;
  };
  // now this means operator<<(P1::vector< int >)
  std::ostream &operator<<(const vector< int > &v) {}
  void doSomething() {
    vector< int > v;
    // ...
    writeObject(v); // OK: instantiates writeObject(P1::vector< int >)
                    //     using above operator<<
  }
}


-- end note ]

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.

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.]

The typename disambiguator for dependent names

In a template definition, a name that is dependent on a template parameter is not considered to be a type unless the keyword typename is used:

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


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

The template disambiguator for dependent names

Similarly, in a template definition, a dependent name 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;