Почему имя из зависимого базового класса не разрешается без this-> или using-объявления?
GCC и Clang отвергают value и greet с "was not declared in this scope", хотя оба — члены Base<T>. MSVC при этом тот же код принимает.
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
}
};
Найдите ошибку и объясните причину.
При поиске фазы 1 компилятор не просматривает зависимые базы, ведь специализация могла бы изменить содержимое Base<T>. Поэтому неквалифицированное value трактуется как не-член и не находится. Сделав его зависимым — this->value или Base<T>::value — мы откладываем поиск до инстанциации, когда база известна.
- ✗Добавлять
this->лишь там, куда указывает ошибка, пропуская другие неквалифицированные имена базы - ✗Считать код переносимым, раз MSVC его компилирует — MSVC исторически пропускает строгий поиск фазы 1
- ✗Думать, что независимая база (
Base<int>) имеет ту же проблему — она есть только у зависимых баз
- →Почему GCC и Clang отвергают этот код, а старый MSVC принимает?
- →Когда
Base<T>::valueпредпочтительнееthis->value?
The bug
template <class T>
struct Base {
int value = 42;
void greet() {}
};
template <class T>
struct Derived : Base<T> {
int read() {
return value; // ошибка: 'value' не объявлено в этой области
}
void run() {
greet(); // ошибка: 'greet' не объявлено в этой области
}
};
GCC и Clang отвергают value и greet. Сообщение обычно гласит "there are no arguments to 'greet' that depend on a template parameter, so a declaration of 'greet' must be available".
Почему так происходит
Имена в шаблоне разрешаются в две фазы. В фазе 1 (при разборе определения шаблона) компилятор ищет неквалифицированные имена. Базовый класс Base<T> — зависимый: его содержимое зависит от T, и программист мог бы написать специализацию Base<SomeType> вообще без члена value. Поэтому стандарт запрещает компилятору заглядывать в зависимые базы во время фазы 1. Неквалифицированное value рассматривается как имя не-члена и не находится.
Исправление
Сделайте имя зависимым, чтобы его поиск отложился до фазы 2 (инстанциации), когда конкретный Base<T> уже известен:
template <class T>
struct Derived : Base<T> {
int read() {
return this->value; // 1) через this->
}
int read2() {
return Base<T>::value; // 2) явная квалификация
}
};
Либо внесите имя в область производного класса один раз using-объявлением:
template <class T>
struct Derived : Base<T> {
using Base<T>::value;
using Base<T>::greet;
int read() { return value; } // теперь ОК
void run() { greet(); } // теперь ОК
};
Подводный камень
MSVC исторически использует отложенный разбор шаблонов и принимает неисправленный код — поэтому код, собирающийся на MSVC, может не собраться на GCC/Clang. Не полагайтесь на это: добавляйте this-> или using-объявление с самого начала.