(Смотрите также мой ответ на C ++ 11 )
Чтобы проанализировать программу на C ++, компилятор должен знать, являются ли определенные имена типами или нет. Следующий пример демонстрирует это:
t * f;
Как это должно быть проанализировано? Для многих языков компилятору не нужно знать значение имени для анализа и, в основном, знать, какое действие выполняет строка кода. Однако в C ++ вышеприведенное может дать совершенно разные интерпретации в зависимости от того, что t
означает. Если это тип, то это будет объявление указателя f
. Однако, если это не тип, это будет умножение. Таким образом, стандарт C ++ говорит в пункте (3/7):
Некоторые имена обозначают типы или шаблоны. В общем, всякий раз, когда встречается имя, необходимо определить, обозначает ли это имя одну из этих сущностей, прежде чем продолжить анализ программы, которая его содержит. Процесс, который определяет это, называется поиском имени.
Как компилятор узнает, на что t::x
ссылается имя , если t
ссылается на параметр типа шаблона? x
это может быть статический элемент данных типа int, который может быть умножен, или в равной степени это может быть вложенный класс или typedef, который может уступить объявлению. Если имя имеет это свойство - его нельзя найти, пока не будут известны фактические аргументы шаблона, - тогда оно называется зависимым именем (оно «зависит» от параметров шаблона).
Вы можете порекомендовать просто подождать, пока пользователь не создаст экземпляр шаблона:
Давайте подождем, пока пользователь не создаст экземпляр шаблона, а затем выясним настоящее значение t::x * f;
.
Это будет работать и фактически допускается Стандартом как возможный подход к реализации. Эти компиляторы в основном копируют текст шаблона во внутренний буфер, и только когда требуется создание экземпляра, они анализируют шаблон и, возможно, обнаруживают ошибки в определении. Но вместо того, чтобы беспокоить пользователей шаблона (бедных коллег!) Из-за ошибок, допущенных автором шаблона, другие реализации предпочитают проверять шаблоны на ранних этапах и сообщают об ошибках в определении как можно скорее, прежде чем даже произойдет создание экземпляра.
Поэтому должен быть способ сообщить компилятору, что определенные имена являются типами, а определенные имена - нет.
Ключевое слово "typename"
Ответ таков: мы решаем, как компилятор должен это проанализировать. Если t::x
это зависимое имя, то нам нужно добавить префикс к нему, typename
чтобы сказать компилятору, что он должен разобрать его определенным образом. Стандарт говорит в (14,6 / 2):
Предполагается, что имя, используемое в объявлении или определении шаблона и которое зависит от параметра-шаблона, не будет называть тип, если только применимый поиск имени не найдет имя типа или имя не будет квалифицировано ключевым словом typename.
Существует множество имен, для которых typename
нет необходимости, потому что компилятор может при помощи поиска подходящего имени в определении шаблона выяснить, как анализировать саму конструкцию - например, с помощью параметра T *f;
when, когда T
есть параметр шаблона типа. Но t::x * f;
чтобы быть декларацией, она должна быть написана как typename t::x *f;
. Если вы опускаете ключевое слово, а имя принимается как нетиповое, но когда экземпляр находит, что оно обозначает тип, компилятор выдает обычные сообщения об ошибках. Иногда ошибка, следовательно, дается во время определения:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
Синтаксис допускает typename
только перед квалифицированными именами - поэтому считается, что неквалифицированные имена всегда относятся к типам, если они это делают.
Аналогичная ошибка существует для имен, которые обозначают шаблоны, на что намекает вводный текст.
Ключевое слово "template"
Помните первоначальную цитату выше, и как стандарт требует специальной обработки для шаблонов? Давайте возьмем следующий невинно выглядящий пример:
boost::function< int() > f;
Это может выглядеть очевидным для читателя. Не так для компилятора. Представьте себе следующее произвольное определение boost::function
и f
:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
Это действительно правильное выражение ! Он использует оператор меньше чем для сравнения boost::function
с нулем ( int()
), а затем использует оператор больше чем для сравнения результата bool
с f
. Однако, как вы, возможно, знаете, boost::function
в реальной жизни это шаблон, поэтому компилятор знает (14.2 / 3):
После того, как name lookup (3.4) обнаружит, что имя является именем шаблона, если за этим именем следует символ <, то <всегда берется как начало списка аргументов шаблона, а не как имя, за которым следует меньше чем оператор.
Теперь мы вернулись к той же проблеме, что и с typename
. Что если мы еще не можем знать, является ли имя шаблоном при разборе кода? Нам нужно будет вставить template
непосредственно перед именем шаблона, как указано 14.2/4
. Это выглядит так:
t::template f<int>(); // call a function template
Имена шаблонов могут появляться не только после, ::
но и после ->
или .
в доступе члена класса. Вам также нужно вставить ключевое слово:
this->template f<int>(); // call a function template
зависимости
Для людей, у которых на полке толстые стандартные книги и которые хотят знать, о чем именно я говорил, я немного расскажу о том, как это указано в Стандарте.
В объявлениях шаблона некоторые конструкции имеют разные значения в зависимости от того, какие аргументы шаблона вы используете для создания экземпляра шаблона: выражения могут иметь разные типы или значения, переменные могут иметь разные типы или вызовы функций могут в конечном итоге вызывать разные функции. Обычно говорят, что такие конструкции зависят от параметров шаблона.
Стандарт точно определяет правила в зависимости от того, является ли конструкция зависимой или нет. Он разделяет их на логически разные группы: одна ловит типы, другая ловит выражения. Выражения могут зависеть от их значения и / или типа. Итак, мы добавили типичные примеры:
- Зависимые типы (например, параметр шаблона типа
T
)
- Выражения, зависящие от значения (например, нетипичный параметр шаблона
N
)
- Типозависимые выражения (например, приведение к параметру шаблона типа
(T)0
)
Большинство правил интуитивно понятны и создаются рекурсивно: например, тип, сконструированный как, T[N]
является зависимым типом, если N
является выражением, зависящим от значения, или T
является зависимым типом. Подробности этого можно прочитать в разделе (14.6.2/1
) для зависимых типов, (14.6.2.2)
для зависимых от типа выражений и (14.6.2.3)
для зависимых от значения выражений.
Зависимые имена
Стандарт немного неясен относительно того , что именно является зависимым именем . При простом чтении (вы знаете, принцип наименьшего удивления) все, что он определяет как зависимое имя, является особым случаем для имен функций ниже. Но поскольку ясно, что T::x
также необходимо искать в контексте реализации, оно также должно быть зависимым именем (к счастью, с середины C ++ 14 комитет начал искать способы исправления этого запутанного определения).
Чтобы избежать этой проблемы, я прибег к простой интерпретации стандартного текста. Из всех конструкций, которые обозначают зависимые типы или выражения, их подмножество представляет имена. Поэтому эти имена являются «зависимыми именами». Название может принимать разные формы - Стандарт гласит:
Имя - это использование идентификатора (2.11), идентификатора оператора-функции (13.5), идентификатора функции преобразования (12.3.2) или идентификатора шаблона (14.2), который обозначает объект или метку (6.6.4, 6.1)
Идентификатор просто обычная последовательность символов / цифр, в то время как рядом два являются operator +
и operator type
формой. Последняя форма есть template-name <argument list>
. Все это имена, и при стандартном использовании в Стандарте имя может также включать квалификаторы, которые говорят, в каком пространстве имен или классе следует искать имя.
Зависимое от значения выражение 1 + N
- это не имя, а имя N
. Подмножество всех зависимых конструкций, которые являются именами, называется зависимым именем . Однако имена функций могут иметь разное значение в разных экземплярах шаблона, но, к сожалению, это общее правило не учитывается.
Имена зависимых функций
Не в первую очередь проблема этой статьи, но все же стоит упомянуть: Имена функций являются исключением, которые обрабатываются отдельно. Имя функции идентификатора зависит не само по себе, а от зависимых от типа выражений аргументов, используемых в вызове. В примере f((T)0)
, f
является зависимым именем. В стандарте это указано в (14.6.2/1)
.
Дополнительные заметки и примеры
В достаточном количестве случаев нам нужны как typename
и template
. Ваш код должен выглядеть следующим образом
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
Ключевое слово template
не всегда должно появляться в последней части имени. Он может появляться в середине перед именем класса, которое используется в качестве области видимости, как в следующем примере
typename t::template iterator<int>::value_type v;
В некоторых случаях ключевые слова запрещены, как описано ниже
На имя зависимого базового класса вам запрещено писать typename
. Предполагается, что данное имя является именем типа класса. Это верно как для имен в списке базового класса, так и в списке инициализатора конструктора:
template <typename T>
struct derive_from_Has_type : /* typename */ SomeBase<T>::type
{ };
В объявлениях об использовании нельзя использовать template
после последнего ::
, и комитет C ++ сказал, что не работает над решением.
template <typename T>
struct derive_from_Has_type : SomeBase<T> {
using SomeBase<T>::template type; // error
using typename SomeBase<T>::type; // typename *is* allowed
};