Правило 42: Усвойте оба значения ключевого слова typename
Правило 42: Усвойте оба значения ключевого слова typename
Вопрос: какая разница между «class» и «typename» в следующем объявлении шаблона:
template <class T> class Widget; // использует “class”
template <typename T> class Widget; // использует “typename”
Ответ: никакой. Когда в шаблоне объявляется параметр типа, class и type-name означают абсолютно одно и то же. Некоторые программисты предпочитают всегда писать class, потому что это слово короче. Другие (включая меня) предпочитают typename, поскольку оно говорит о том, что параметром не обязательно должен быть тип класса. Некоторые разработчики используют typename, когда допускается любой тип, и резервируют слово class для случаев, когда допускается только тип, определяемый пользователем. Но с точки зрения C++, class и typename в объявлении параметра шаблона означают в точности одно и то же.
Однако не всегда в C++ ключевые слова class и typename эквивалентны. Иногда вы обязаны использовать typename. Чтобы понять – когда именно, поговорим о двух типах имен, на которые можно ссылаться в шаблоне.
Предположим, что у нас есть шаблон функции, принимающей в качестве параметра совместимый с STL-контейнер, содержащий объекты, которые могут быть присвоены величинам типа int. Далее предположим, что эта функция просто печатает значение второго элемента. Это не очень содержательная функция, которая к тому же и реализована по-дурацки. Как я уже говорил, она даже не будет компилироваться, но забудьте об этом на время – все это не так глупо, как кажется:
template <typename C> // печатает второй
void print2nd(const C& container) // элемент контейнера
{ // это некорректный C++!
if (container.size() >= 2) {
C::const_iterator iter(container.begin()); // получить итератор,
// указывающий на первый
// элемент
++iter; // сместиться на второй
// элемент
int value = *iter; // скопировать элемент в int
std::cout << value; // напечатать int
}
}
Я выделил в этой функции две локальные переменные – iter и value. Типом iter является C::const_iterator – он зависит от параметра шаблона C. Имена в шаблоне, которые зависят от параметра шаблона, называются зависимыми именами. Зависимое имя внутри класса я буду называть вложенным зависимым именем. C::const_iterator – это вложенное зависимое имя. Фактически это даже вложенное зависимое имя типа, то есть вложенное имя, которое относится к типу.
Другая локальная переменная в print2nd – value – имеет тип int, а int – это имя, которое не зависит ни от какого параметра шаблона. Такие имена называются независимыми.
Вложенные зависимые имена могут стать причиной затруднений на этапе синтаксического анализа исходного текста компилятором. Например, предположим, что мы реализуем print2nd еще более глупо, написав в начале такой код:
template <typename C> // печатает второй элемент контейнера
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 <typename C>
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 <typename C> // это корректный С++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}
Общее правило просто: всякий раз, когда вы обращаетесь к вложенному зависимому имени в шаблоне, вы должны предварить его словом typename (скоро я опишу исключение).
Слово typename следует использовать для идентификации только вложенных зависимых имен типов; для других имен оно не применяется. Вот пример шаблона функции, который принимает и контейнер, и итератор для этого контейнера:
template <typename C> // допускается typename (как и “class”)
void f(const C& container, // typename не допускается
typename C::iterator iter); // typename требуется
C не является вложенным зависимым именем типа (оно не вложено внутрь чего-либо, зависимого от параметра шаблона), поэтому его не нужно предварять словом typename при объявлении контейнера, но C::iterator – это вложенное зависимое имя типа, поэтому перед ним следует поставить typename.
Из правила «typename должно предварять вложенные зависимые имена типов» есть исключение: typename не должно предварять вложенные зависимые имена типов в списке базовых классов или в идентификаторе базового класса в списке инициализации членов. Например:
template <typename T>
class Derived: public Base<T>::Nested { // список базовых классов:
public: // typename не допускается
explicit Derived(int x)
:Base<T>::Nested(x) // идентификатор базового класса
{ // в списке инициализации членов:
// typename не допускается
typename Base<T>::Nested temp; // использование вложенного
... // зависимого имени типа не как
} // идентификатора базового
... // класса в списке инициализации
}; // членов: typename необходимо
Такая несогласованность несколько раздражает, но по мере приобретения опыта вы перестанете ее замечать.
Рассмотрим еще один пример использования typename, потому нечто подобное можно встретить в реальном коде. Предположим, что мы пишем шаблон функции, которая принимает итератор, и хотим сделать локальную копию – temp – объекта, на который этот итератор указывает. Это можно сделать примерно так:
template <typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}
Не пугайтесь при виде выражения std::iterator_traits<IterT>::value_type. Здесь просто используются стандартные классы-характеристики (traits) (см. правило 47). Так, на C++ говорят «тип того, на что указывает объект типа *IterT». В этом предложении объявлена локальная переменная (temp) того же типа, что и объекты, на которые указывает IterT, а затем она инициализирована значением, на которое указывает iter. Если IterT будет типа vector<int>::iterator, то temp будет иметь тип int. Если же IterT будет типа vector<string>::iterator, то temp будет иметь тип string. Поскольку std::iterator_traits<IterT>::value_type – это вложенное зависимое имя типа (value_type вложено внутрь iterator_traits<IterT>, а IterT – параметр шаблона), мы должны предварить его словом typename.
Если вам неприятно даже видеть выражение std::iterator_traits<IterT>::value_type, представьте, каково набирать его на клавиатуре. Если вы, как и большинство программистов, считаете, что набрать такое более одного раза немыслимо, определите псевдоним для этого типа посредством typedef. Для имен членов классов-характеристик, к каковым относится value_type, (см. в правиле 47 информацию о классах-характеристиках), принято соглашение, согласно которому имя typedef должно совпадать с именем члена. Таким образом, определение локального typedef обычно выглядит так:
template <typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}
Многих программистов соседство typedef и typename поначалу раздражает, но это логическое следствие из правила обращения к вложенным зависимым именам типов. Вы скоро привыкнете. К тому же у вас есть на то веские причины. Сколько раз вы готовы напечатать std::iterator_traits<IterT>::value_type?
В качестве заключительного замечания я должен упомянуть, что не все компиляторы настаивают на строгом выполнении правил, касающихся ключевого слова typename. Некоторые принимают код, в котором typename требуется, но пропущено; некоторые принимают код, где typename присутствует, но не допускается; и некоторые (обычно это касается старых компиляторов) отвергают typename даже там, где оно необходимо. Это значит, что взаимосвязи между typename и вложенными зависимыми имен типов могут стать причиной некоторых не очень серьезных ошибок при переносе программ на другую платформу.
Что следует помнить
• В объявлениях параметров шаблона ключевые слова class и typename взаимозаменяемы.
• Используйте typename для идентификации вложенных зависимых имен типов, если они не встречаются в списке базовых классов или в качестве идентификатора базового класса в списках инициализации членов.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
Вводные слова
Вводные слова Одна из главнейших задач при работе на компьютере – манипулирование данными: создание, модификация, копирование, перемещение и так далее. И тут первое дело – это организация их размещения. Это понятие включает в себя широкий круг частных вопросов – схемы
Исключить слова
Исключить слова «Яндекс» позволяет исключать из выдачи страницы с упоминанием определенных слов. Используйте оператор ~~. Слева от него укажите, что искать, а справа – какие страницы исключать из поиска. Если вы ищете информацию о Задорнове (но не о министре), введите
Слова признательности
Слова признательности Эта книга вряд ли увидела бы свет без серьезной поддержки со стороны старой доброй The Washington Post. Лучшего места работы для журналиста не сыскать. Мы благодарим председателя совета директоров и генерального директора The Washington Post Company Дона Грэма,
Синхронизация с помощью ключевого слова lock в C#
Синхронизация с помощью ключевого слова lock в C# Первой из возможностей, которую вы можете применить в C# для синхронизации доступа к совместно используемым ресурсам, является использование ключевого слова lock. Это ключевое слово позволяет определить контекст операторов,
1. Пустые значения (Empty-значения)
1. Пустые значения (Empty-значения) Пустое значение – это просто одно из множества возможных значений какого-то вполне определенного типа данных.Перечислим наиболее «естественные», непосредственные пустые значения (т. е. пустые значения, которые мы могли бы выделить
2. Неопределенные значения ( Null-значения)
2. Неопределенные значения (Null-значения) Слово Null используется для обозначения неопределенных значений в базах данных.Чтобы лучше понять, какие значения понимаются под неопределенными, рассмотрим таблицу, являющуюся фрагментом базы данных: Итак, неопределенное
Мастера Слова
Мастера Слова Нанимайте хороших писателейЕсли вы задумываетесь над тем, какого рода специалиста можно еще пригласить на не занятое место, — наймите того, кто лучше других умеет вести документацию. Не важно кто он — дизайнер, программист, специалист по продажам или кто-то
2.3 Ключевые Слова
2.3 Ключевые Слова Следующие идентификаторы зарезервированы для использовния в качестве ключевых слов и не могут использоваться иным образом:asm auto break case char class const continue default delete do double else enum extern float for friend goto if inline int long new operator overload public register return short sizeof static struct switch this typedef union unsigned
Ключевые слова
Ключевые слова Список «key words» – это список ключевых слов, которые Book Designer использует для поиска названий глав в процессе автоматического форматирования книги. Вы можете добавить или исключить ключевые слова из списка при помощи кнопок, расположенных справа от
Ключевые слова
Ключевые слова Количество допустимых ключевых слов на каждом сайте разное. Больше всего их на сайте Sutterstock.com. Здесь можно выбрать до 50 различных ключевых слов, подглядывая в русско-английский словарь. Как уже писалось выше, фотографии крайне часто получают отказ по
Слова, слова, слова… Автор: Евгений Козловский.
Слова, слова, слова… Автор: Евгений Козловский. © 2004, Издательский дом | http://www.computerra.ru/Журнал «Домашний компьютер» | http://dk.compulenta.ru/Этот материал Вы всегда сможете найти по его постоянному адресу: /2006/120/276445/Интересно, сколько двенадцатизначных чисел вы способны оперативно