Другая локальная переменная в print2nd – value – имеет тип int, а int – это имя, которое не зависит ни от какого параметра шаблона. Такие имена называются независимыми.
Вложенные зависимые имена могут стать причиной затруднений на этапе синтаксического анализа исходного текста компилятором. Например, предположим, что мы реализуем print2nd еще более глупо, написав в начале такой код:
template // печатает второй элемент контейнера
void print2nd(const C& container) // это некорректный C++!
{
C::const_iterator *x;
...
}
Выглядит так, будто мы объявили x как локальную переменную – указатель на C::const_iterator. Но это только видимость, поскольку мы «знаем», что C::const_iterator является типом. А что, если в классе C есть статический член данных по имени const_iterator и что, если x будет именем глобальной переменной? В этом случае приведенный код не будет объявлять локальную переменную, а окажется умножением C::const_iterator на x! Звучит невероятно, но это возможно, и авторы синтаксических анализаторов исходного кода на C++ должны позаботиться обо всех возможных вариантах входных данных, даже самых сумасшедших.
Пока о C ничего не известно, мы не можем узнать, является ли C::const_iterator типом или нет, а во время разбора шаблона print2nd компилятор ничего о C не знает. В C++ предусмотрено правило, разрешающее эту неопределенность: если синтаксический анализатор встречает вложенное зависимое имя в шаблоне, он предполагает, что это не имя типа, если только вы не укажете это явно. По умолчанию вложенные зависимые имена не являются типами. Есть исключение из этого правила, о котором я расскажу чуть ниже.
Имея это в виду, посмотрите опять на начало print2nd:
template
void print2nd(const C& container)
{
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // предполагается, что
... // это не имя типа
Теперь должно быть ясно, почему это некорректный C++. Объявление iter имеет смысл только в случае, если C::const_iterator является типом, но мы не сообщили C++ об этом, потому C++ предполагает, что это не так. Чтобы исправить ситуацию, мы должны сообщить C++, что C::const_iterator – это тип. Для этого мы помещаем ключевое слово typename непосредственно перед ним:
template // это корректный С++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
Общее правило просто: всякий раз, когда вы обращаетесь к вложенному зависимому имени в шаблоне, вы должны предварить его словом typename (скоро я опишу исключение).
Слово typename следует использовать для идентификации только вложенных зависимых имен типов; для других имен оно не применяется. Вот пример шаблона функции, который принимает и контейнер, и итератор для этого контейнера:
template // допускается typename (как и “class”)
void f(const C& container, // typename не допускается
typename C::iterator iter); // typename требуется
C не является вложенным зависимым именем типа (оно не вложено внутрь чего-либо, зависимого от параметра шаблона), поэтому его не нужно предварять словом typename при объявлении контейнера, но C::iterator – это вложенное зависимое имя типа, поэтому перед ним следует поставить typename.
Из правила «typename должно предварять вложенные зависимые имена типов» есть исключение: typename не должно предварять вложенные зависимые имена типов в списке базовых классов или в идентификаторе базового класса в списке инициализации членов. Например:
template
class Derived: public Base::Nested { // список базовых классов:
public: // typename не допускается
explicit Derived(int x)
:Base::Nested(x) // идентификатор базового класса
{ // в списке инициализации членов:
// typename не допускается
typename Base::Nested temp; // использование вложенного
... // зависимого имени типа не как
} // идентификатора базового
... // класса в списке инициализации
}; // членов: typename необходимо
Такая несогласованность несколько раздражает, но по мере приобретения опыта вы перестанете ее замечать.
Рассмотрим еще один пример использования typename, потому нечто подобное можно встретить в реальном коде. Предположим, что мы пишем шаблон функции, которая принимает итератор, и хотим сделать локальную копию – temp – объекта, на который этот итератор указывает. Это можно сделать примерно так:
template
void workWithIterator(IterT iter)
{
typename std::iterator_traits::value_type temp(*iter);
Читать дальше
Конец ознакомительного отрывка
Купить книгу