Instantiation - Abstraction Mechanisms - The C++ Programming Language (2013)

The C++ Programming Language (2013)

Part III: Abstraction Mechanisms

26. Instantiation

For every complex problem, there is an answer that is clear, simple, and wrong.

– H. L. Mencken

Introduction

Template Instantiation

When Is Instantiation Needed?; Manual Control of Instantiation

Name Binding

Dependent Names; Point-of-Definition Binding; Point-of-Instantiation Binding; Multiple Instantiation Points; Templates and Namespaces; Overaggressive ADL; Names from Base Classes

Advice

26.1. Introduction

One of the great strengths of templates is that they are an extremely flexible mechanism for composition of code. To produce impressive code quality, a compiler combines code (information) from

• the template definition and its lexical environment,

• the template arguments and their lexical environment, and

• the environment of the template’s use.

The key to the resulting performance is that the compiler is able to look at code from those contexts simultaneously and weave it together in ways that use all information available. The problem with this is that code in a template definition isn’t as localized as we would prefer (all other things being equal). Sometimes, we can get confused about what a name used in a template definition refers to:

• Is it a local name?

• Is it a name associated with a template argument?

• Is it a name from a base class in a hierarchy?

• Is it a name from a named namespace?

• Is it a global name?

This chapter discusses such questions related to name binding and considers their implications for programming styles.

• Templates were introduced in §3.4.1 and §3.4.2.

Chapter 23 gives a detailed introduction to templates and the use of template arguments.

Chapter 24 introduces generic programming and the key notion of concepts.

Chapter 25 presents details of class templates and function templates and introduces the notion of specialization.

Chapter 27 discusses the relationship between templates and class hierarchies (supporting generic and object-oriented programming).

Chapter 28 focuses on templates as a language for generating classes and functions.

Chapter 29 presents a larger example of how the language facilities and programming techniques can be used in combination.

26.2. Template Instantiation

Given a template definition and a use of that template, it is the implementation’s job to generate correct code. From a class template and a set of template arguments, the compiler needs to generate the definition of a class and the definitions of those of its member functions that were used in the program (and only those; §26.2.1). From a template function and a set of template arguments, a function needs to be generated. This process is commonly called template instantiation.

The generated classes and functions are called specializations. When we need to distinguish between generated specializations and specializations explicitly written by the programmer (§25.3), we refer to generated specializations and explicit specializations, respectively. An explicit specialization is often referred to as a user-defined specialization, or simply a user specialization.

To use templates in nontrivial programs, a programmer must understand the basics of how names used in a template definition are bound to declarations and how source code can be organized (§23.7).

By default, the compiler generates classes and functions from the templates used in accordance with the name-binding rules (§26.3). That is, a programmer need not state explicitly which versions of which templates must be generated. This is important because it is not easy for a programmer to know exactly which versions of a template are needed. Often, templates that the programmer hasn’t even heard of are used in the implementation of libraries, and sometimes templates that the programmer does know of are used with unknown template argument types. For example, the standard-library map4.4.3, §31.4.3) is implemented in terms of a red-black tree template with data types and operations unknown to all but the most curious user. In general, the set of generated functions needed can be known only by recursive examination of the templates used in application code libraries. Computers are better suited than humans for doing such analysis.

On the other hand, it is sometimes important for a programmer to be able to state specifically where code should be generated from a template (§26.2.2). By doing so, the programmer gains detailed control over the context of the instantiation.

26.2.1. When Is Instantiation Needed?

It is necessary to generate a specialization of a class template only if the class’s definition is needed (§iso.14.7.1). In particular, to declare a pointer to some class, the actual definition of a class is not needed. For example:

class X;
X* p; // OK: no definition of X needed
X a; // error: definition of X needed

When defining template classes, this distinction can be crucial. A template class is not instantiated unless its definition is actually needed. For example:

template<typename T>
class Link {
Link* suc; //
OK: no definition of Link needed (yet)
// ...
};

Link<int>* pl; // no instantiation of Link<int> needed (yet)

Link<int> lnk; // now we need to instantiate Link<int>

A place where a template is used defines a point of instantiation (§26.3.3).

An implementation instantiates a template function only if that function has been used. By “used” we mean “called or had its address taken.” In particular, instantiation of a class template does not imply the instantiation of all of its member functions. This allows the programmer an important degree of flexibility when defining a template class. Consider:

template<typename T>
class List {
//
...
void sort();
};


class Glob {
//
... no comparison operators ...
};

void f(List<Glob>& lb, List<string>& ls)
{
ls.sort();
//
... use operations on lb, but not lb.sort() ...
}

Here, List<string>::sort() is instantiated, but List<Glob>::sort() isn’t. This both reduces the amount of code generated and saves us from having to redesign the program. Had List<Glob>::sort() been generated, we would have had to either add the operations needed by List::sort() toGlob, redefine sort() so that it wasn’t a member of List (the better design anyway), or use some other container for Globs.

26.2.2. Manual Control of Instantiation

The language does not require any explicit user action to achieve template instantiation. However, it does provide two mechanisms to help the user take control when needed. The need sometimes arises from a wish to

• optimize the compile-and-link process by eliminating redundant replicated instantiations, or

• know exactly which point of instantiation is used to eliminate surprises from complicated name-binding contexts.

An explicit instantiation request (often simply called an explicit instantiation) is a declaration of a specialization prefixed by the keyword template (not followed by <):

template class vector<int>; // class
template int& vector<int>::operator[](int); // member function
template int convert<int,double>(double); // nonmember function

A template declaration starts with template<, whereas plain template starts an instantiation request. Note that template prefixes a complete declaration; just stating a name is not sufficient:

template vector<int>::operator[]; // syntax error
template convert<int,double>; // syntax error

As in template function calls, the template arguments that can be deduced from the function arguments can be omitted (§23.5.1). For example:

template int convert<int,double>(double); // OK (redundant)
template int convert<int>(double); // OK

When a class template is explicitly instantiated, every member function is also instantiated.

The link-time and recompilation efficiency impact of instantiation requests can be significant. I have seen examples in which bundling most template instantiations into a single compilation unit cut the compile time from a number of hours to the equivalent number of minutes.

It is an error to have two definitions for the same specialization. It does not matter if such multiple specializations are user-defined (§25.3), implicitly generated (§23.2.2), or explicitly requested. However, a compiler is not required to diagnose multiple instantiations in separate compilation units. This allows a smart implementation to ignore redundant instantiations and thereby avoid problems related to composition of programs from libraries using explicit instantiation. However, implementations are not required to be smart. Users of “less smart” implementations must avoid multiple instantiations. The worst that will happen if they don’t is that their program won’t link; there will be no silent changes of meaning.

To complement explicit instantiation requests, the language provides explicit requests not to instantiate (usually called extern templates). The obvious use is to have one explicit instantiation for a specialization and use extern templates for its use in other translation units. This mirrors the classical use of one definition and many declarations (§15.2.3). For example:

#include "MyVector.h"

extern template class MyVector<int>; //
suppresses implicit instantiation
// explicitly instantiate elsewhere
void foo(MyVector<int>& v)
{
//
... use the vector in here ...
}

The “elsewhere” might look something like this:

#include "MyVector.h"

template class MyVector<int>; //
instantiate in this translation unit; use this point of instantiation

In addition to generating specializations for all members of a class, the explicit instantiation also determines a single point of instantiation so that other points of instantiation (§26.3.3) can be ignored. One use of this is to place the explicit instantiation in a shared library.

26.3. Name Binding

Define template functions to minimize dependencies on nonlocal information. The reason is that a template will be used to generate functions and classes based on unknown types and in unknown contexts. Every subtle context dependency is likely to surface as a problem for somebody – and that somebody is unlikely to want to know the implementation details of the template. The general rule of avoiding global names as much as possible should be taken especially seriously in template code. Thus, we try to make template definitions as self-contained as possible and to supply much of what would otherwise have been global context in the form of template arguments (e.g., traits; §28.2.4, §33.1.3). Use concepts to document dependencies on template arguments (§24.3).

However, it is not unusual that some nonlocal names must be used to achieve the most elegant formulation of a template. In particular, it is more common to write a set of cooperating template functions than to write just one self-contained function. Sometimes, such functions can be class members, but not always. Sometimes, nonlocal functions are the best choice. Typical examples of that are sort()’s calls to swap() and less()25.3.4). The standard-library algorithms are a large-scale example (Chapter 32). When something needs to be nonlocal, prefer a named namespace to the global scope. Doing so preserves some locality.

Operations with conventional names and semantics, such as +, *, [], and sort(), are another source of nonlocal name use in a template definition. Consider:

bool tracing;

template<typename T>
T sum(std::vector<T>& v)
{
T t {};
if (tracing)
cerr << "sum(" << &v << ")\n";
for (int i = 0; i!=v.size(); i++)
t = t + v[i];
return t;
}

// ...

#include<quad.h>

void f(std::vector<Quad>& v)
{
Quad c = sum(v);
}

The innocent-looking template function sum() depends on several names that are not explicitly specified in its definition, such as tracing, cerr, and the + operator. In this example, + is defined in <quad.h>:

Quad operator+(Quad,Quad);

Importantly, nothing related to Quad is in scope when sum() is defined, and the writer of sum() cannot be assumed to know about class Quad. In particular, the + may be defined later than sum() in the program text, and even later in time.

The process of finding the declaration for each name explicitly or implicitly used in a template is called name binding. The general problem with template name binding is that three contexts are involved in a template instantiation and they cannot be cleanly separated:

[1] The context of the template definition

[2] The context of the argument type declaration

[3] The context of the use of the template

When defining a function template, we want to assure that enough context is available for the template definition to make sense in terms of its actual arguments without picking up “accidental stuff” from the environment of a point of use. To help with this, the language separates names used in a template definition into two categories:

[1] Dependent names: names that depend on a template parameter. Such names are bound at a point of instantiation (§26.3.3). In the sum() example, the definition of + can be found in the instantiation context because it takes operands of the template argument type.

[2] Nondependent names: names that don’t depend on a template parameter. Such names are bound at the point of definition of the template (§26.3.2). In the sum() example, the template vector is defined in the standard header <vector>, and the Boolean tracing is in scope when the definition of sum() is encountered by the compiler.

To be considered, both dependent and independent names must either be in scope at their point of use or be found by argument-dependent lookup (ADL; §14.2.4).

The following subsections go into considerable technical detail about how dependent and non-dependent names in a template definition are bound for a specialization. For complete details, see §iso.14.6.

26.3.1. Dependent Names

The simplest definition of “N depends on a template parameter T” would be “N is a member of T.” Unfortunately, this doesn’t quite suffice; addition of Quads (§26.3) is a counter-example. Consequently, a function call is said to depend on a template argument if and only if one of these conditions holds:

[1] The type of the actual argument depends on a template parameter T according to the type deduction rules (§23.5.2), for example, f(T(1)), f(t), f(g(t)), and f(&t), assuming that t is a T.

[2] The function called has a parameter that depends on T according to the type deduction rules (§23.5.2), for example, f(T), f(list<T>&), and f(const T*).

Basically, the name of a called function is dependent if it is obviously dependent by looking at its arguments or at its formal parameters. For example:

template<typename T>
T f(T a)
{
return g(a); //
OK: a is a dependent name and therefore so is g
}

class Quad { /* ... */ };
void g(Quad);

int z = f(Quad{2}); //
f's g is bound to g(Quad)

A call that by coincidence has an argument that matches an actual template parameter type is not dependent. For example:

class Quad { /* ... */ };

template<typename T>
T ff(T a)
{
return gg(Quad{1}); //
error: no gg() in scope and gg(Quad{1}) doesn't depend on T
}

int gg(Quad);

int zz = ff(Quad{2});

Had gg(Quad{1}) been considered dependent, its meaning would have been most mysterious to a reader of the template definition. If a programmer wants gg(Quad) to be called, gg(Quad)’s declaration should be placed before the definition of ff() so that gg(Quad) is in scope when ff() is analyzed. This is exactly the same rule as for non-template function definitions (§26.3.2).

By default, a dependent name is assumed to name something that is not a type. So, to use a dependent name as a type, you have to say so, using the keyword typename. For example:

template<typename Container>
void fct(Container& c)
{
Container::value_type v1 = c[7]; //
syntax error: value_type is assumed to be a non-type name
typename Container::value_type v2 = c[9]; // OK: value_type assumed to name a type
auto v3 = c[11]; // OK: let the compiler figure it out
// ...
}

We can avoid such awkward use of typename by introducing a type alias (§23.6). For example:

template<typename T>
using Value_type<T> = typename T::value_type;

template<typename Container>
void fct2(Container& c)
{
Value_type<Container> v1 = c[7]; //
OK
// ...
}

Naming a member template after a . (dot), –>, or :: requires similar use of the keyword template. For example:

class Pool { // some allocator
public:
template<typename T> T* get();
template<typename T> void release(T*);
//
...
};

template<typename Alloc>
void f(Alloc& all)
{
int* p1 = all.get<int>(); //
syntax error: get is assumed to name a non-template
int* p2 = all.template get<int>(); // OK: get() is assumed to be a template
// ...
}

void user(Pool& pool){
{
f(pool);
//
...
}

Compared to the use of typename to explicitly state that a name is assumed to name a type, the use of template to explicitly state that a name is assumed to name a template is rare. Note the difference in the placement of the disambiguating keyword: typename appears before the qualified name and template immediately before the template name.

26.3.2. Point-of-Definition Binding

When the compiler sees a template definition, it determines which names are dependent (§26.3.1). If a name is dependent, looking for its declaration is postponed until instantiation time (§26.3.3).

Names that do not depend on a template argument are treated like names that are not in templates; they must be in scope (§6.3.4) at the point of definition. For example:

int x;

template<typename T>
T f(T a)
{
++x; //
OK: x is in scope
++y; // error: no y in scope, and y doesn't depend on T
return a; // OK: a is dependent
}

int y;

int z = f(2);

If a declaration is found, that declaration is used even if a “better” declaration might be found later. For example:

void g(double);
void g2(double);

template<typename T>
int ff(T a)
{
g2(2); //
call g2(double);
g3(2); // error: no g3() in scope
g(2); // call g(double); g(int) is not in scope
// ...
}

void g(int);
void g3(int);

int x = ff(a);

Here, ff() will call g(double). The definition of g(int) comes too late to be considered – just as if ff() had not been a template or if g had named a variable.

26.3.3. Point-of-Instantiation Binding

The context used to determine the meaning of a dependent name (§26.3.1) is determined by the use of a template with a given set of arguments. This is called a point of instantiation for that specialization (§iso.14.6.4.1). Each use of a template for a given set of template arguments defines a point of instantiation. For a function template, that point is in the nearest global or namespace scope enclosing its use, just after the declaration that contains that use. For example:

void g(int);

template<typename T>
void f(T a)
{
g(a); //
g is bound at a point of instantiation
}
void h(int i)
{
extern void g(double);
f(i);
}
//
point of declaration for f<int>

The point of instantiation for f<int>() is outside h(). This is essential to ensure that the g() called in f() is the global g(int) rather than the local g(double). An unqualified name used in a template definition can never be bound to a local name. Ignoring local names is essential to prevent a lot of nasty macro-like behavior.

To enable recursive calls, the point of declaration for a function template is after the declaration that instantiates it. For example:

void g(int);

template<typename T>
void f(T a)
{
g(a); //
g is bound at a point of instantiation
if (i) h(a–1); // h is bound at a point of instantiation
}

void h(int i)
{
extern void g(double);
f(i);
}
//
point of declaration for f<int>

Here, having the point of instantiation after the definition of h() is necessary to allow the (indirectly recursive) call h(a–1).

For a template class or a class member, the point of instantiation is just before the declaration containing its use.

template<typename T>
class Container {
vector<T> v; //
elements
// ...
public:
void sort(); //
sort elements
// ...
};

// point of instantiation of Container<int>
void f()
{
Container<int> c; //
point of use
c.sort();
}

Had the point of instantiation been after f() the call c.sort() would have failed to find the definition of Container<int>.

Relying on template arguments to make dependencies explicit simplifies our thinking about the template code and even allows us to access local information. For example:

void fff()
{
struct S { int a,b; };
vector<S> vs;
//
...
}

Here, S is a local name, but since we use it as an explicit argument, rather than trying to bury its name in the definition of vector, we have no potentially surprising subtleties.

So why don’t we completely avoid nonlocal names in template definitions? That would certainly solve the technical problem with name lookup, but – as for ordinary function and class definitions – we want to be able to use “other functions and types” freely in our code. Turning every dependency into an argument can lead to very messy code. For example:

template<typename T>
void print_sorted(vector<T>& v)
{
sort(v.begin(),v.end());
for (const auto T& x : v)
cout << x << '\n';
}

void use(vector<string>& vec)
{
//
...
print_sorted(vec); // sort using std::sort, then print using std::cout
}

Here, we are using just two nonlocal names (sort and cout, both from the standard library). To eliminate those we would need to add parameters:

template<typename T, typename S>
void print_sorted(vector<T>& v, S sort, ostream& os)
{
sort(v.begin(),v.end());
for (const auto T& x : v)
os << x << '\n';
}

void fct(vector<string>& vec)
{
//
...
using Iter = decltype(vs.begin()); // vec's iterator type
print_sorted(some_vec,std::sort<Iter>,std::cout);
}

In this trivial case, there is a lot to be said for removing the dependence on the global name cout. However, in general, as illustrated by sort(), adding parameters can make the code much more verbose without necessarily making it easier to understand.

Also, if the name-binding rules for templates were radically more restrictive than the rules for non-template code, writing template code would be a completely separate skill from writing non-template code. Templates and non-template code would no longer interoperate simply and freely.

26.3.4. Multiple Instantiation Points

A template specialization may be generated

• at any point of instantiation (§26.3.3),

• at any point subsequent to that in a translation unit,

• or in a translation unit specifically created for generating specializations.

This reflects three obvious strategies an implementation can use for generating specializations:

[1] Generate a specialization the first time a call is seen.

[2] At the end of a translation unit, generate all specializations needed for it.

[3] Once every translation unit of a program has been seen, generate all specializations needed for the program.

All three strategies have strengths and weaknesses, and combinations of these strategies are also possible.

So, a template used several times with the same set of template arguments has several points of instantiation. A program is illegal if it is possible to construct two different meanings by choosing different points of instantiation. That is, if the bindings of a dependent or a nondependent name can differ, the program is illegal. For example:

void f(int); // here, I take care of ints

namespace N {
class X {};
char g(X,int);
}

template<typename T>
void ff(T t, double d)
{
f(d); //
f is bound to f(int)
return g(t,d); // g might be bound to g(X,int)
}

auto x1 = ff(N::X{},1.1); // ff<N::X,double>; may bind g to N::g(X,int), narrowing 1.1 to 1

Namespace N { // reopen N to take care of doubles
double g(X,double);
}

auto x2 = ff(N::X,2.2); //
ff<N::X,double>; binds g to N::g(X,double); the best match

For ff() we have two instantiation points. For the first call, we could generate the specialization at the initialization of x1 and get g(N::X,int) called. Alternatively, we could wait and generate the specialization at the end of the translation unit and get g(N::X,char) called. Consequently, the call ff(N::X{},1.1) is an error.

It is sloppy programming to call an overloaded function between two of its declarations. However, looking at a large program, a programmer would have no reason to suspect a problem. In this particular case, a compiler could catch the ambiguity. However, similar problems can occur in separate translation units, and then detection becomes much harder (for both compilers and programmers). An implementation is not obliged to catch problems of this kind.

To avoid surprising name bindings, try to limit context dependencies in templates.

26.3.5. Templates and Namespaces

When a function is called, its declaration can be found even if it is not in scope, provided it is declared in the same namespace as one of its arguments (§14.2.4). This is important for functions called in template definitions because it is the mechanism by which dependent functions are found during instantiation. The binding of dependent names is done (§iso.14.6.4.2) by looking at

[1] the names in scope at the point where the template is defined, plus

[2] the names in the namespace of an argument of a dependent call (§14.2.4).

For example:

namespace N {
class A { /*
... */ };
char f(A);
}

char f(int);

template<typename T>
char g(T t)
{
return f(t); //
choose f() depending on what T is
}

char f(double);

char c1 = g(N::A()); // causes N::f(N::A) to be called
char c2 = g(2); // causes f(int) to be called
char c3 = g(2.1); // causes f(int) to be called; f(double) not considered

Here, f(t) is clearly dependent, so we can’t bind f at the point of definition. To generate a specialization for g<N::A>(N::A), the implementation looks in namespace N for functions called f() and finds N::f(N::A).

The f(int) is found because it is in scope at the point of definition of the template. The f(double) is not found because it is not in scope at the point of definition of the template (§iso.14.6.4.1), and argument-dependent lookup (§14.2.4) does not find a global function that takes only arguments of built-in types. I find it easy to forget that.

26.3.6. Overaggressive ADL

Argument-dependent lookup (often referred to as ADL) is very useful to avoid verbosity (§14.2.4). For example:

#include <iostream>

int main()
{
std::cout << "Hello, world" << endl;//
OK because of ADL
}

Without argument-dependent lookup, the endl manipulator would not be found. As it is, the compiler notices that the first argument to << is an ostream defined in std. Therefore, it looks for endl in std and finds it (in <iostream>).

However, ADL can be “too aggressive” when combined with unconstrained templates. Consider:

#include<vector>
#include<algorithm>
//
...

namespace User {
class Customer { /*
... */ };
using Index = std::vector<Customer*>;


void copy(const Index&, Index&, int deep); // deep or shallow copy depending on the value of deep

void algo(Index& x, Index& y)
{
//
...
copy(x,y,false);// error
}
}

It would be a good guess that the author of User meant for User::alg() to call User::copy(). However, that’s not what happens. The compiler notices that Index really is a vector defined in std and looks to see if a relevant function is available in std. In <algorithm>, it finds:

template<typename In, typename Out>
Out copy(In,In,Out);

Obviously, this general template is a perfect match for copy(x,y,false). On the other hand, the copy() in User can be called only with a bool-to-int conversion. For this example, as for equivalent examples, the compiler’s resolution is a surprise to most programmers and a source of very obscure bugs. Using ADL to find fully general templates is arguably a language design error. After all, std::copy() requires a pair of iterators (not just two arguments of the same type, such as the two Indexes). The standard says so, but the code does not. Many such problems can be solved by the use of concepts (§24.3, §24.3.2). For example, had the compiler known that std::copy() required two iterators, a nonobscure error would have been the result.

template<typename In, typename Out>
Out copy(In p1, In p2, Out q)
{
static_assert(Input_iterator<In>(), "copy(): In is not an input iterator");
static_assert(Output_iterator<Out>(), :copy(): Out is not an output iterator");
static_assert(Assignable<Value_type<Out>,Value_type<In>>(), "copy(): value type mismatch");
//
...
}

Better still, the compiler would have noticed that std::copy() wasn’t even a valid candidate for that call and User::copy() would have been invoked. For example (§28.4):

template<typename In, typename Out,
typename = enable_if(Input_iterator<In>()
&& Output_iterator<Out>()
&& Assignable<Value_type<Out>,Value_type<In>>())>
Out copy(In p1, In p2, Out q)
{
//
...
}

Unfortunately, many such templates are in libraries that a user cannot modify (e.g., the standard library).

It is a good idea to avoid fully general (completely unconstrained) function templates in headers that also contain type definitions, but that’s hard to avoid. If you need one, protecting it with a constraints check is often worthwhile.

What can a user do if a library has trouble-causing unconstrained templates? Often, we know which namespace our function should come from, so we can be specific about that. For example:

void User::algo(Index& x, Index& y)
{
User::copy(x,y,false); //
OK
// ...
std::swap(*x[i],*x[j]); // OK: only std::swap is considered
}

If we don’t want to be specific about which namespace to use, but want to make sure that a particular version of a function is considered by function overloading, we can use a using-declaration (§14.2.2). For example:

template<typename Range, typename Op>
void apply(const Range& r, Op f)
{
using std::begin;
using std::end;
for (auto& x : r)
f(x);
}

Now, the standard begin() and end() are in the overload set used by the range-for to traverse the Range (unless Range has members begin() and end(); §9.5.1).

26.3.7. Names from Base Classes

When a class template has a base class, it can access names from that base. As for other names, there are two distinct possibilities:

• The base class depends on a template argument.

• The base class does not depend on a template argument.

The latter case is simple and treated just like base classes in classes that are not templates. For example:

void g(int);

struct B {
void g(char);
void h(char);
};

template<typename T>
class X : public B {
public:
void h(int);
void f()
{
g(2); //
call B::g(char)
h(2); // call X::h(int)
}
//
...
};

As ever, local names hide other names, so h(2) binds to X::h(int) and B::h(char) is never considered. Similarly, the call g(2) is bound to B::g(char) without any concern for functions declared outside X. That is, the global g() is never considered.

For base classes that depend on a template parameter, we have to be a bit more careful and explicit about what we want. Consider:

void g(int);

struct B {
void g(char);
void h(char);
};

template<typename T>
class X : public T {
public:
void f()
{
g(2); //
call ::g(int)
}
//
...
};

void h(X<B> x)
{
x.f();
}

Why doesn’t g(2) call B::g(char) (as in the previous example)? Because g(2) isn’t dependent on the template parameter T. It is therefore bound at the point of definition; names from the template argument T (which happens to be used as a base class) are not (yet) known and therefore not considered. If we want to have names from a dependent class considered, we must make the dependency clear. We have three ways of doing that:

• Qualify a name with a dependent type (e.g., T::g).

• Say that the name refers an object of this class (e.g., this–>g).

• Bring the name into scope with a using-declaration (e.g., using T::g).

For example:

void g(int);
void g2(int);

struct B {
using Type = int;
void g(char);
void g2(char)
};

template<typename T>
class X : public T {
public:
typename T::Type m; //
OK
Type m2; // error (no Type in scope)

using T::g2(); // bring T::g2() into scope

void f()
{
this–>g(2); //
call T::g
g(2); // call ::g(int); surprise?
g2(2); // call T::g2
}
//
...
};

void h(X<B> x)
{
x.f();
}

Only at the point of instantiation can we know if the argument used for the parameter T (here B) has the required names.

It is easy to forget to qualify names from a base, and the qualified code often looks a bit verbose and messy. However, the alternative would be that a name in a template class would sometimes bind to a base class member and sometimes to a global entity depending on the template argument. That is not ideal either, and the language rule supports the rule of thumb that a template definition should be as self-contained as possible (§26.3).

Qualifying access to dependent base members of a template can be a nuisance. However, explicit qualifications help maintainers, so the initial author shouldn’t grumble too much about the extra typing. A common occurrence of this problem is when a whole class hierarchy is templatized. For example:

template<typename T>
class Matrix_base { //
memory for matrices, operations of all elements
// ...
int size() const { return sz; }
protected:
int sz; //
number of elements
T* elem; // matrix elements
};

template<typename T, int N>
class Matrix : public Matrix_base<T> { //
N-dimensional matrix
// ...
T* data() // return pointer to element storage
{
return this–>elem;
}
};

Here, the this–> qualification is required.

26.4. Advice

[1] Let the compiler/implementation generate specializations as needed; §26.2.1.

[2] Explicitly instantiate if you need exact control of the instantiation environment; §26.2.2.

[3] Explicitly instantiate if you optimize the time needed to generate specializations; §26.2.2.

[4] Avoid subtle context dependencies in a template definition; §26.3.

[5] Names must be in scope when used in a template definition or findable through argument-dependent lookup (ADL); §26.3, §26.3.5.

[6] Keep the binding context unchanged between instantiation points; §26.3.4.

[7] Avoid fully general templates that can be found by ADL; §26.3.6.

[8] Use concepts and/or static_assert to avoid using inappropriate templates; §26.3.6.

[9] Use using-declarations to limit the reach of ADL; §26.3.6.

[10] Qualify names from a template base class with –> or T:: as appropriate; §26.3.7.