Я решил протестировать один из примеров в «Эффективном C ++», и я не получил ожидаемого результата. Итак, по-видимому, этот (упрощенный) код не должен компилироваться:
template <class T>
struct A {
void f(){}
};
template <class T>
struct B : public A <T> {
void f2() { f(); } // calling base function - will not compile
};
Вот объяснение (имена классов изменены для простоты):
Приведенный выше код не будет компилироваться, по крайней мере, с помощью совместимых компиляторов. Такие компиляторы будут жаловаться, что
f
не существует Мы это видимf
находится в базовом классе, но компиляторы не будут искать его там.Нам нужно понять почему.
Проблема в том, что когда компиляторы сталкиваются с определением шаблона классаB
, Oни
не знаю, от какого класса он наследует. Конечно этоA<T>
, ноT
это параметр шаблона,
тот, который не будет известен до позже (когдаB
создается) Не зная чтоT
есть, нет никакого способа узнать, что классA<T>
похоже. В частности, нет способа узнать, есть ли у негоf
функция.
Мой компилятор (Visual Studio) не возражает … Он даже не показывает никаких предупреждений.
Является ли приведенный выше код правильным или нет?
template <class T>
struct A {
void f(){}
};
template <class T>
struct B : public A <T> {
void f2() { f(); } // calling base function - will not compile
};
В производном шаблоне выражение f()
не зависит ни от одного аргумента шаблона, поэтому компилятор пытается разрешить его во время поиска первой фазы. На данный момент шаблон еще не был создан с типом, и компилятор не будет смотреть в базу A<T>
, Причина в том, что компилятор не может знать, существует ли для типа экземпляра специализация A<T>
это может не содержать каких-либо членов.
Решение состоит в том, чтобы сделать выражение зависимым, самый простой способ состоит в том, чтобы this->
:
template <typename T>
void B<T>::f2() { this->f(); }
Поскольку выражение теперь зависит, поиск задерживается до второй фазы, где тип заменяется и A<T>
это конкретный тип. Другой альтернативой является определение класса, в котором он определен:
template <typename T>
void B<T>::f2() { A<T>::f(); }
Снова выражение становится зависимым и будет разрешено во время второго этапа. Основное отличие состоит в том, что во втором случае вызов квалифицирован и, следовательно, не использует динамическую диспетчеризацию. Если A<T>::f()
был виртуальным, он все равно выполнит A<T>::f()
и не окончательное переопределение.
Код правильный? Нет. VS принимает это? Да.
Это известное несоответствие в компиляторе Visual Studio, которое не реализует двухфазный поиск. Он задерживает весь поиск внутри шаблона до второй фазы, и в этот момент поиск завершается успешно.
Других решений пока нет …