Чтобы разобрать программу на 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;
, когда 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):
После того, как поиск по имени (3.4) обнаружит, что имя является именем шаблона, если за этим именем следует знак <, <является
всегда принимается за начало списка аргументов шаблона и никогда за именем, за которым следует меньше
оператор. </p>
Теперь мы вернулись к той же проблеме, что и с typename
. Что если мы еще не можем знать, является ли имя шаблоном при разборе кода? Нам нужно будет вставить template
непосредственно перед именем шаблона, как указано 14.2/4
. Это выглядит так:
t::template f<int>(); // call a function template
Имена шаблонов могут встречаться не только после ::
, но и после ->
или .
в доступе члена класса. Вам тоже нужно вставить ключевое слово:
this->template f<int>(); // call a function template
1097 * Зависимость *
Для людей, у которых на полке толстые стандартные книги и которые хотят знать, о чем именно я говорил, я немного расскажу о том, как это указано в Стандарте.
В объявлениях шаблона некоторые конструкции имеют разные значения в зависимости от того, какие аргументы шаблона вы используете для создания экземпляра шаблона: выражения могут иметь разные типы или значения, переменные могут иметь разные типы или вызовы функций могут в конечном итоге вызывать разные функции. Обычно говорят, что такие конструкции зависят от параметров шаблона.
Стандарт точно определяет правила в зависимости от того, является ли конструкция зависимой или нет. Он разделяет их на логически разные группы: одна ловит типы, другая ловит выражения. Выражения могут зависеть от их значения и / или типа. Итак, мы добавили типичные примеры:
Зависимые типы (например, параметр шаблона типа 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
};