Why does a name from a dependent base class fail to resolve without this-> or a using-declaration?
GCC and Clang reject value and greet with "was not declared in this scope", even though both are members of Base<T>. MSVC accepts the same code.
template <class T>
struct Base {
int value = 42;
void greet() {}
};
template <class T>
struct Derived : Base<T> {
int read() {
return value; // error: 'value' was not declared in this scope
}
void run() {
greet(); // error: 'greet' was not declared in this scope
}
};
Identify the bug and explain the cause.
During phase-1 lookup the compiler doesn't search dependent bases, because a specialization could change what Base<T> contains. So an unqualified value is treated as a non-member name and not found. Making it dependent — this->value or Base<T>::value — defers lookup to instantiation, when the base is known.
- ✗Adding
this->only where the error points, missing other unqualified base names in the same class - ✗Assuming the code is portable because MSVC compiles it — MSVC historically skips strict phase-1 lookup
- ✗Believing a non-dependent base (e.g.
Base<int>) has the same problem — only dependent bases do
- →Why do GCC and Clang reject this code while older MSVC accepts it?
- →When would
Base<T>::valuebe preferable tothis->value?
The bug
template <class T>
struct Base {
int value = 42;
void greet() {}
};
template <class T>
struct Derived : Base<T> {
int read() {
return value; // error: 'value' was not declared in this scope
}
void run() {
greet(); // error: 'greet' was not declared in this scope
}
};
GCC and Clang reject both value and greet. The message usually reads "there are no arguments to 'greet' that depend on a template parameter, so a declaration of 'greet' must be available".
Why it happens
Names inside a template are resolved in two phases. In phase 1 (when the template definition is parsed) the compiler looks up unqualified names. The base class Base<T> is dependent: its contents depend on T, and a programmer could write a specialization Base<SomeType> with no value member at all. The standard therefore forbids the compiler from looking into dependent bases during phase 1. The unqualified value is treated as a non-member name and is not found.
The fix
Make the name dependent so its lookup is deferred to phase 2 (instantiation), when the concrete Base<T> is known:
template <class T>
struct Derived : Base<T> {
int read() {
return this->value; // 1) via this->
}
int read2() {
return Base<T>::value; // 2) explicit qualification
}
};
Or pull the name into the derived scope once with a using-declaration:
template <class T>
struct Derived : Base<T> {
using Base<T>::value;
using Base<T>::greet;
int read() { return value; } // now OK
void run() { greet(); } // now OK
};
Pitfall
MSVC historically uses delayed template parsing and accepts the unfixed code — so code that builds on MSVC may fail on GCC/Clang. Do not rely on this: add this-> or a using-declaration from the start.