Вызов функции_члена шаблонного базового класса из функции производного шаблонного класса

70
20 марта 2022, 00:40
template <class T>
class Base {
public:
    void f() const { cout << "Base\n"; }
};
template <class Tp >
class D1 : public Base<Tp> {
public:
    void g() const
    {
        f();           // вариант_ошибка
        this->f();     // первый вариант вызова
        Base<Tp>::f(); // второй вариант вызова
    }
};

Знаю, что нужно вызвать f() первыйм или вторым вариантом. Но почему является ошибкой просто вызов f(); ? Ведь и так ясно, что вызывается для данного обьекта, а базовый класс инстанцируется также как и производный.... Какое обьяснение вы дадите(потому что я не уверен, что правильно и четко все это понимаю)?...

Answer 1

Короткий ответ: потому что так сказано в стандарте языка. При выполнении поиска неквалифицированных имен базовые классы, являющиеся зависимыми типами, не рассматриваются.

Длинный ответ: тип, зависящий от параметра шаблона, является зависимым типом (в данном примере - Base<Tp>), а вложенное в зависимый тип имя - зависимым именем (в данном примере - Base<Tp>::f). При этом, во-первых, по неквалифицированному имени f не видно, действительно ли автор кода хотел использовать зависимое имя в данном контексте. И, во-вторых, синтаксис неквалифицированного имени не предоставляет никаких средств разрешения неоднозначностей между разными типами зависимых имен.

Реализация шаблонов в С++ критически опирается на то, что компилятор при трансляции определения шаблона всегда должен иметь возможность однозначно определять синтаксический смысл каждой конструкции, еще до того как станут известны конкретные значения шаблонных параметров. В частности компилятор должен уметь отличать объявления от выражений, имена функций от имен типов, имена шаблонов от имен не-шаблонов и т.п. Пока не известны конкретные значения параметров шаблона, природа многих зависимых имен в общем случае не известна. Из-за этого возникают неоднозначности, которые невозможно разрешить автоматически. Базовые классы, зависящие от параметра шаблона, как раз являются примером зависимого типа. Вложенные в них имена будут зависимыми именами "неизвестной природы".

Например, если в определении шаблона D1<Tp> вы ссылаетесь на вложенные имена из какого-то другого шаблона (неважно, базового или совершенно постороннего класса) вот так

Base<Tp>::a *x;
Base<Tp>::с<int()> y;

компилятор сразу при трансляции определения вашего шаблона должен знать, является ли первая строчка объявлением переменной x типа "указатель" (т.е. Base<Tp>::a - тип) или просто вычисления произведения двух переменных (т.е. Base<Tp>::a - переменная). И является ли вторая строчка объявлением переменной y типа Base<Tp>::с<0> (т.е. Base<Tp>::с - шаблон) или просто вычислением выражения (Base<Tp>::с < 0) > y (т.е. Base<Tp>::с - переменная)

В вашем примере имеет место такая же ситуация: даже если компилятор бы понимал, что имя f, по вашей задумке, является вложенным именем из класса Base<Tp>, все равно в процессе трансляции определения шаблона он не мог бы определить, что такое f. Это имя функции, имя переменной или имя типа? Если f - имя функции или переменной, то f() - это вызов функции. Если f - имя типа, то f() - это выражение, создающее временный объект типа f.

Разрешение таких неоднозначностей - это задача, которая возлагается на плечи пользователя. Эти неоднозначности пользователь обязан при необходимости разрешать при помощи явного указания ключевых слов typename и template. Но этот способ разрешения неоднозначностей поддерживается только в применении к квалифицированным именам.

Base<Tp>::f(); // 'f' - имя функции или переменной
typename Base<Tp>::f(); // 'f' - имя типа
typename Base<Tp>::template f<>(); // 'f' - имя шаблона типа
Base<Tp>::template f<>(); // 'f' - имя шаблона функции или переменной

Неквалифицированные имена такого синтаксиса не поддерживают (возможно из-за наличия других синтаксических конфликтов, возможно просто из-за того, что задача уже решена более радикально).

(Обратите также внимание, что если бы компилятор рассматривал неквалифицированные имена, как потенциально зависимые, то в таких контекстах он вынужден был бы рассматривать все (!) неквалифицированные имена, как потенциально зависимые. Ибо любое имя может оказаться членом конкретной специализации базового класса. Это привело бы к тому, что пользователь пришлось бы снабжать ключевыми словами typename и template практически все употребления неквалифицированных имен типов и шаблонов в таком коде.)

При использовании синтаксиса доступа к члену класса (через this-> или (*this).) неоднозначностей между типами, переменными и функциями не возникает, поэтому в вашем примере его достаточно. Неоднозначность между шаблонами и нешаблонами сохраняется, поэтому если бы ваше f было шаблоном, вам все равно пришлось бы использовать ключевое слово template при обращении к f

this->template f<>();

P.S. Ключевым моментом, еще раз, является то, что проблема неоднозначности возникает на ранней стадии, еще до того, как станут известны конкретные значения параметров шаблона, т.е. на том этапе процесса трансляции шаблона, который обычно называют первой фазой поиска имен. Компилятор MSVC++ старых версий (и в режиме совместимости с этими старыми версиями) неправильно реализовывал двухфазный поиск имен - он всегда откладывал этот процесс до того момента, когда параметры шаблона уже известны, т.е. до второй фазы поиска имен. В такой ситуации вышеописанные неоднозначности, разумеется, не возникают. По этой причине в компиляторе MSVC++ до недавнего времени и "невидимые" имена из базовых классов прекрасно находились, и требования обязательного применения ключевых слов typename и template (как описано выше) не было.

Answer 2

Просто этой функции нет в области видимости наследника. Добавьте совершенно другую функцию с такой же сигнатурой и убедитесь, что никаких ошибок нет. Для этого и сделано.

Почему так сделано в шаблонах именно: потому что если явно квалифицировать имя, то оно станет зависимым и поиск будет проведен в контексте инстанциации. Иначе f не является зависимым и найден не будет.

READ ALSO
Память под контейнеры выделяется в стеки или куче?

Память под контейнеры выделяется в стеки или куче?

как выделяется память под контейнеры vector, map в с++

96
Bitset. Изменение битов элементов

Bitset. Изменение битов элементов

Есть некоторые массивы

88
Что делает следующая строчка?

Что делает следующая строчка?

Есть динамический массив под названием buffer, который хранит в себе строку в стиле С(Например какое-нибудь предложение: Hello world) Что делает данная...

54
Как в MySql осуществить обход древа

Как в MySql осуществить обход древа

Имеется вот такая таблица

102