Страницы

Поиск по вопросам

воскресенье, 29 декабря 2019 г.

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

#cpp #наследование #шаблоны_с++


template 
class Base {
public:
    void f() const { cout << "Base\n"; }
};

template 
class D1 : public Base {
public:
    void g() const
    {
        f();           // вариант_ошибка
        this->f();     // первый вариант вызова
        Base::f(); // второй вариант вызова

    }
};


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


Ответы

Ответ 1



Короткий ответ: потому что так сказано в стандарте языка. При выполнении поиска неквалифицированных имен базовые классы, являющиеся зависимыми типами, не рассматриваются. Длинный ответ: тип, зависящий от параметра шаблона, является зависимым типом (в данном примере - Base), а вложенное в зависимый тип имя - зависимым именем (в данном примере - Base::f). При этом, во-первых, по неквалифицированному имени f не видно, действительно ли автор кода хотел использовать зависимое имя в данном контексте. И, во-вторых, синтаксис неквалифицированного имени не предоставляет никаких средств разрешения неоднозначностей между разными типами зависимых имен. Реализация шаблонов в С++ критически опирается на то, что компилятор при трансляции определения шаблона всегда должен иметь возможность однозначно определять синтаксический смысл каждой конструкции, еще до того как станут известны конкретные значения шаблонных параметров. В частности компилятор должен уметь отличать объявления от выражений, имена функций от имен типов, имена шаблонов от имен не-шаблонов и т.п. Пока не известны конкретные значения параметров шаблона, природа многих зависимых имен в общем случае не известна. Из-за этого возникают неоднозначности, которые невозможно разрешить автоматически. Базовые классы, зависящие от параметра шаблона, как раз являются примером зависимого типа. Вложенные в них имена будут зависимыми именами "неизвестной природы". Например, если в определении шаблона D1 вы ссылаетесь на вложенные имена из какого-то другого шаблона (неважно, базового или совершенно постороннего класса) вот так Base::a *x; Base::с y; компилятор сразу при трансляции определения вашего шаблона должен знать, является ли первая строчка объявлением переменной x типа "указатель" (т.е. Base::a - тип) или просто вычисления произведения двух переменных (т.е. Base::a - переменная). И является ли вторая строчка объявлением переменной y типа Base::с<0> (т.е. Base::с - шаблон) или просто вычислением выражения (Base::с < 0) > y (т.е. Base::с - переменная) В вашем примере имеет место такая же ситуация: даже если компилятор бы понимал, что имя f, по вашей задумке, является вложенным именем из класса Base, все равно в процессе трансляции определения шаблона он не мог бы определить, что такое f. Это имя функции, имя переменной или имя типа? Если f - имя функции или переменной, то f() - это вызов функции. Если f - имя типа, то f() - это выражение, создающее временный объект типа f. Разрешение таких неоднозначностей - это задача, которая возлагается на плечи пользователя. Эти неоднозначности пользователь обязан при необходимости разрешать при помощи явного указания ключевых слов typename и template. Но этот способ разрешения неоднозначностей поддерживается только в применении к квалифицированным именам. Base::f(); // 'f' - имя функции или переменной typename Base::f(); // 'f' - имя типа typename Base::template f<>(); // 'f' - имя шаблона типа Base::template f<>(); // 'f' - имя шаблона функции или переменной Неквалифицированные имена такого синтаксиса не поддерживают (возможно из-за наличия других синтаксических конфликтов, возможно просто из-за того, что задача уже решена более радикально). (Обратите также внимание, что если бы компилятор рассматривал неквалифицированные имена, как потенциально зависимые, то в таких контекстах он вынужден был бы рассматривать все (!) неквалифицированные имена, как потенциально зависимые. Ибо любое имя может оказаться членом конкретной специализации базового класса. Это привело бы к тому, что пользователь пришлось бы снабжать ключевыми словами typename и template практически все употребления неквалифицированных имен типов и шаблонов в таком коде.) При использовании синтаксиса доступа к члену класса (через this-> или (*this).) неоднозначностей между типами, переменными и функциями не возникает, поэтому в вашем примере его достаточно. Неоднозначность между шаблонами и нешаблонами сохраняется, поэтому если бы ваше f было шаблоном, вам все равно пришлось бы использовать ключевое слово template при обращении к f this->template f<>(); P.S. Ключевым моментом, еще раз, является то, что проблема неоднозначности возникает на ранней стадии, еще до того, как станут известны конкретные значения параметров шаблона, т.е. на том этапе процесса трансляции шаблона, который обычно называют первой фазой поиска имен. Компилятор MSVC++ старых версий (и в режиме совместимости с этими старыми версиями) неправильно реализовывал двухфазный поиск имен - он всегда откладывал этот процесс до того момента, когда параметры шаблона уже известны, т.е. до второй фазы поиска имен. В такой ситуации вышеописанные неоднозначности, разумеется, не возникают. По этой причине в компиляторе MSVC++ до недавнего времени и "невидимые" имена из базовых классов прекрасно находились, и требования обязательного применения ключевых слов typename и template (как описано выше) не было.

Ответ 2



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

Комментариев нет:

Отправить комментарий