Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the C++ standard specify that unqualified names in a template are non-dependent?

Tags:

c++

templates

Why is it that the C++ standard specify that unqualified names in a template are non-dependent?

e.g.

template<typename T>
class Base
{
public:
    T x;
};

template<typename T>
class C : public Base<T>
{
public:
    bool m() { return x == 0; } // Error: undeclared identifier 'x'
};

Quoting from the accepted answer to an SO question about how to overcome the restriction:

The standard specifies that unqualified names in a template are non-dependent and must be looked up when the template is defined. The definition of a dependent base class is unknown at that time (specializations of the base class template may exist) so unqualified names are unable to be resolved.

However, the quoted and other answers do not specify why this is what the standard specifies. What is the rationale for this restriction?

like image 353
Danra Avatar asked Mar 22 '17 20:03

Danra


People also ask

What is dependent name in C++?

A dependent name is essentially a name that depends on a template parameter. A dependent name can be a type, a non-type, or a template parameter. To express that a dependent name stands for a type or a template, you have to use the keywords typename or template .

What is dependent name?

A dependent name is a name that depends on the type or the value of a template parameter. For example: template<class T> class U : A<T> { typename T::B x; void f(A<T>& y) { *y++; } }; The dependent names in this example are the base class A<T> , the type name T::B , and the variable y .


2 Answers

This isn't quite what the standard actually says. What it actually says is that dependent base classes are not examined during unqualified name lookup. An unqualified name can of course be dependent given the correct context - that's how ADL in templates works: given a template type parameter T, the foo in foo(T()) is a dependent name.

In any event, the reason it can't do this kind of lookup is straightforward: at template definition time, you have no idea what the dependent base class is going to look like, thanks to specializations that may come in later, so you can't do any meaningful lookup. Every misspelled identifier in a template with a dependent base class can't be diagnosed until when it is instantiated, if ever. And since every unqualified identifier might have been from a dependent base class, you'll need some way to answer "is this a type?" and "is this a template?", since the answers to those questions affect parsing. That means something like the dreaded typename and template keywords, but in far more places.

like image 122
T.C. Avatar answered Oct 10 '22 04:10

T.C.


I think it is a matter of consistency. Consider this, a bit modified example:

header file:

template<typename T>
class Base
{
public:
    T x;
};

extern int x;

template<typename T>
class C : public Base<T>
{
public:
    bool m() { return x == 0; }
};

and source file:

template<>
class Base<int> // but could be anything
{
public:
    // no x here
};

int main()
{
  C<char> c1;
  c1.m(); // may select ::x or Base<char>::x
  C<int> c2;
  c2.m(); // has only one choice: ::x
  return(0);
}

Wording in the standard guarantees that compiler will either error out or select whatever symbol it sees at the point of template definition. If the compiler was to defer name resolution to template instantiation it may then select different objects visible at this point and which may surprise the developer.

If the developer WANTS to access dependent name he is required to state this explicitly and he shouldn't be then taken off guard.

Please also note that if the x wasn't available at the point of template definition the following code would break (unexpectedly) One Definition Rule:

one.cpp

#include <template_header>

namespace {
int x;
}

void f1()
{
  C<int> c1;
}

two.cpp

#include <template_header>

namespace {
char x;
}

void f2()
{
  C<int> c2;
}

One would expect that c1 and c2 variables are of the same type and can for example be safely passed to a function taking C<int> const & parameter, but basically the two variables have the same type name but the implementations differ.

like image 8
Tomek Avatar answered Oct 10 '22 05:10

Tomek