16

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?

Community
  • 1
  • 1
Danra
  • 9,546
  • 5
  • 59
  • 117

3 Answers3

16

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.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • 1
    But by just adding `using T::x` at the class scope, the restriction disappears, even though specializations may exist which make `x` invalid (in which case I guess instantiation would give an error). So I suppose that in a similar fashion, in a compiler's second pass over the templates, it could either use `T::x` on its own, or figure out that the unqualified name `x` is invalid. – Danra Mar 22 '17 at 20:52
  • 2
    @Danra Yes, it can figure it out at instantiation time. The point is that the compiler would not be able to do any typo checking at template definition time, because every typo is a possible dependent base member. – T.C. Mar 22 '17 at 20:58
  • That's a good point. Perhaps that's the rationale behind it. Theoretically, though, I guess that if compilers and the machines they run on were fast enough so that code editors could fully compile all the code, including template instantiation on the fly, typo catching could just be deferred to that stage. – Danra Mar 22 '17 at 21:13
  • 1
    @Danra The author of the template code may not be the author of the code code instantiating the template. (Think header-only template libraries.) – T.C. Mar 22 '17 at 21:15
8

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.

Tomek
  • 4,554
  • 1
  • 19
  • 19
  • 1
    We can always make up a rule in this alternate universe that says that whatever we found in, say, the primary template of the dependent base is what `x` binds to at instantiation time. – T.C. Mar 22 '17 at 21:00
  • I believe this would break full and partial template specialization then. – Tomek Mar 22 '17 at 21:03
1

Whether compliant with the standard or not, in Visual Studio 2015 - this isn't an issue at all. Moreover, despite the comment from Tomek, the compiler takes the correct x

static int x = 42;

template<typename T>
class Base {
public:
    T x;
    Base() { x = 43; }
};

template<>
class Base<int> { };

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

void main() {
    C<int> cint;
    C<char> cchar;

    std::cout << "cint: " << cint.m() << std::endl;
    std::cout << "cchar: " << cchar.m() << std::endl;
}

// Output is:
// cint: 42
// cchar: 43
Uri London
  • 10,631
  • 5
  • 51
  • 81
  • 2
    Good chance this will break though once MSVC's template handling is C++98 standards compliant :) (AFAIK it still isn't in VC++2017, see for example https://blogs.msdn.microsoft.com/vcblog/2016/11/16/permissive-switch/) – Danra Apr 08 '17 at 17:26