В шаблонах, где и почему я должен поставить typename
и template
на зависимых именах?
Что именно являются зависимыми именами в любом случае?
У меня есть следующий код:
template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
// Q: where to add typename/template here?
typedef Tail::inUnion<U> dummy;
};
template< > struct inUnion<T> {
};
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
// ...
template<typename U> struct inUnion {
char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
};
template< > struct inUnion<T> {
};
};
Проблема у меня в typedef Tail::inUnion<U> dummy
очереди. Я вполне уверен, что inUnion
это зависимое имя, и VC ++ вполне прав, подавляя его.
Я также знаю, что я должен быть в состоянии добавить template
куда-нибудь, чтобы сообщить компилятору, что inUnion - это идентификатор шаблона. Но где именно? И следует ли тогда предполагать, что inUnion является шаблоном класса, то есть именует inUnion<U>
тип, а не функцию?
Ответы:
(Смотрите также мой ответ на C ++ 11 )
Чтобы проанализировать программу на C ++, компилятор должен знать, являются ли определенные имена типами или нет. Следующий пример демонстрирует это:
Как это должно быть проанализировано? Для многих языков компилятору не нужно знать значение имени для анализа и, в основном, знать, какое действие выполняет строка кода. Однако в C ++ вышеприведенное может дать совершенно разные интерпретации в зависимости от того, что
t
означает. Если это тип, то это будет объявление указателяf
. Однако, если это не тип, это будет умножение. Таким образом, стандарт C ++ говорит в пункте (3/7):Как компилятор узнает, на что
t::x
ссылается имя , еслиt
ссылается на параметр типа шаблона?x
это может быть статический элемент данных типа int, который может быть умножен, или в равной степени это может быть вложенный класс или typedef, который может уступить объявлению. Если имя имеет это свойство - его нельзя найти, пока не будут известны фактические аргументы шаблона, - тогда оно называется зависимым именем (оно «зависит» от параметров шаблона).Вы можете порекомендовать просто подождать, пока пользователь не создаст экземпляр шаблона:
Это будет работать и фактически допускается Стандартом как возможный подход к реализации. Эти компиляторы в основном копируют текст шаблона во внутренний буфер, и только когда требуется создание экземпляра, они анализируют шаблон и, возможно, обнаруживают ошибки в определении. Но вместо того, чтобы беспокоить пользователей шаблона (бедных коллег!) Из-за ошибок, допущенных автором шаблона, другие реализации предпочитают проверять шаблоны на ранних этапах и сообщают об ошибках в определении как можно скорее, прежде чем даже произойдет создание экземпляра.
Поэтому должен быть способ сообщить компилятору, что определенные имена являются типами, а определенные имена - нет.
Ключевое слово "typename"
Ответ таков: мы решаем, как компилятор должен это проанализировать. Если
t::x
это зависимое имя, то нам нужно добавить префикс к нему,typename
чтобы сказать компилятору, что он должен разобрать его определенным образом. Стандарт говорит в (14,6 / 2):Существует множество имен, для которых
typename
нет необходимости, потому что компилятор может при помощи поиска подходящего имени в определении шаблона выяснить, как анализировать саму конструкцию - например, с помощью параметраT *f;
when, когдаT
есть параметр шаблона типа. Ноt::x * f;
чтобы быть декларацией, она должна быть написана какtypename t::x *f;
. Если вы опускаете ключевое слово, а имя принимается как нетиповое, но когда экземпляр находит, что оно обозначает тип, компилятор выдает обычные сообщения об ошибках. Иногда ошибка, следовательно, дается во время определения:Синтаксис допускает
typename
только перед квалифицированными именами - поэтому считается, что неквалифицированные имена всегда относятся к типам, если они это делают.Аналогичная ошибка существует для имен, которые обозначают шаблоны, на что намекает вводный текст.
Ключевое слово "template"
Помните первоначальную цитату выше, и как стандарт требует специальной обработки для шаблонов? Давайте возьмем следующий невинно выглядящий пример:
Это может выглядеть очевидным для читателя. Не так для компилятора. Представьте себе следующее произвольное определение
boost::function
иf
:Это действительно правильное выражение ! Он использует оператор меньше чем для сравнения
boost::function
с нулем (int()
), а затем использует оператор больше чем для сравнения результатаbool
сf
. Однако, как вы, возможно, знаете,boost::function
в реальной жизни это шаблон, поэтому компилятор знает (14.2 / 3):Теперь мы вернулись к той же проблеме, что и с
typename
. Что если мы еще не можем знать, является ли имя шаблоном при разборе кода? Нам нужно будет вставитьtemplate
непосредственно перед именем шаблона, как указано14.2/4
. Это выглядит так:Имена шаблонов могут появляться не только после,
::
но и после->
или.
в доступе члена класса. Вам также нужно вставить ключевое слово:зависимости
Для людей, у которых на полке толстые стандартные книги и которые хотят знать, о чем именно я говорил, я немного расскажу о том, как это указано в Стандарте.
В объявлениях шаблона некоторые конструкции имеют разные значения в зависимости от того, какие аргументы шаблона вы используете для создания экземпляра шаблона: выражения могут иметь разные типы или значения, переменные могут иметь разные типы или вызовы функций могут в конечном итоге вызывать разные функции. Обычно говорят, что такие конструкции зависят от параметров шаблона.
Стандарт точно определяет правила в зависимости от того, является ли конструкция зависимой или нет. Он разделяет их на логически разные группы: одна ловит типы, другая ловит выражения. Выражения могут зависеть от их значения и / или типа. Итак, мы добавили типичные примеры:
T
)N
)(T)0
)Большинство правил интуитивно понятны и создаются рекурсивно: например, тип, сконструированный как,
T[N]
является зависимым типом, еслиN
является выражением, зависящим от значения, илиT
является зависимым типом. Подробности этого можно прочитать в разделе(14.6.2/1
) для зависимых типов,(14.6.2.2)
для зависимых от типа выражений и(14.6.2.3)
для зависимых от значения выражений.Зависимые имена
Стандарт немного неясен относительно того , что именно является зависимым именем . При простом чтении (вы знаете, принцип наименьшего удивления) все, что он определяет как зависимое имя, является особым случаем для имен функций ниже. Но поскольку ясно, что
T::x
также необходимо искать в контексте реализации, оно также должно быть зависимым именем (к счастью, с середины C ++ 14 комитет начал искать способы исправления этого запутанного определения).Чтобы избежать этой проблемы, я прибег к простой интерпретации стандартного текста. Из всех конструкций, которые обозначают зависимые типы или выражения, их подмножество представляет имена. Поэтому эти имена являются «зависимыми именами». Название может принимать разные формы - Стандарт гласит:
Идентификатор просто обычная последовательность символов / цифр, в то время как рядом два являются
operator +
иoperator type
формой. Последняя форма естьtemplate-name <argument list>
. Все это имена, и при стандартном использовании в Стандарте имя может также включать квалификаторы, которые говорят, в каком пространстве имен или классе следует искать имя.Зависимое от значения выражение
1 + N
- это не имя, а имяN
. Подмножество всех зависимых конструкций, которые являются именами, называется зависимым именем . Однако имена функций могут иметь разное значение в разных экземплярах шаблона, но, к сожалению, это общее правило не учитывается.Имена зависимых функций
Не в первую очередь проблема этой статьи, но все же стоит упомянуть: Имена функций являются исключением, которые обрабатываются отдельно. Имя функции идентификатора зависит не само по себе, а от зависимых от типа выражений аргументов, используемых в вызове. В примере
f((T)0)
,f
является зависимым именем. В стандарте это указано в(14.6.2/1)
.Дополнительные заметки и примеры
В достаточном количестве случаев нам нужны как
typename
иtemplate
. Ваш код должен выглядеть следующим образомКлючевое слово
template
не всегда должно появляться в последней части имени. Он может появляться в середине перед именем класса, которое используется в качестве области видимости, как в следующем примереВ некоторых случаях ключевые слова запрещены, как описано ниже
На имя зависимого базового класса вам запрещено писать
typename
. Предполагается, что данное имя является именем типа класса. Это верно как для имен в списке базового класса, так и в списке инициализатора конструктора:В объявлениях об использовании нельзя использовать
template
после последнего::
, и комитет C ++ сказал, что не работает над решением.источник
typename
применяется, даже когда синтаксис не допускает альтернативных интерпретаций, кроме имен типов на данном этапе?C ++ 11
проблема
Хотя правила в C ++ 03 о том, когда вам нужно
typename
иtemplate
в значительной степени разумные, есть один досадный недостаток в его формулировкеКак можно видеть, нам нужно ключевое слово устранения неоднозначности, даже если компилятор может сам определить, что
A::result_type
может быть толькоint
(и, следовательно, является типом), иthis->g
может быть только шаблоном члена,g
объявленным позже (даже еслиA
где-то явно специализируется, это будет не влияет на код в этом шаблоне, поэтому его значение не может быть затронуто последующей специализациейA
!).Текущий экземпляр
Чтобы улучшить ситуацию, в C ++ 11 язык отслеживает, когда тип ссылается на включающий шаблон. Чтобы знать , что тип должен быть сформирован с использованием определенной формы имени, которая является его собственным именем (в приведенном выше
A
,A<T>
,::A<T>
). Тип, на который ссылается такое имя, известен как текущий экземпляр . Может быть несколько типов, которые являются текущими экземплярами, если тип, из которого сформировано имя, является классом-членом / вложенным (тогдаA::NestedClass
иA
оба являются текущими экземплярами).На основе этого понятия, язык говорит , что
CurrentInstantiation::Foo
,Foo
иCurrentInstantiationTyped->Foo
(напримерA *a = this; a->Foo
), все члены текущего экземпляра , если они будут признаны членами класса , который является текущей конкретизацией или один из его не-зависимых базовых классов (просто делая поиск имени сразу).Ключевые слова
typename
иtemplate
теперь больше не требуются, если классификатор является членом текущего экземпляра. Keypoint здесь помнить, чтоA<T>
это до сих пор имя типа зависит ( в конце концовT
, также зависит от типа). НоA<T>::result_type
известно, что это тип - компилятор «волшебным образом» изучит этот тип зависимых типов, чтобы выяснить это.Это впечатляет, но мы можем сделать лучше? Язык даже идет дальше и требует , чтобы реализация снова смотрела
D::result_type
при создании экземпляраD::f
(даже если она нашла свое значение уже во время определения). Если теперь результат поиска отличается или приводит к неоднозначности, программа имеет неправильную форму и должна быть предоставлена диагностика. Представьте, что произойдет, если мы определилиC
такКомпилятор обязан отлавливать ошибку при создании экземпляра
D<int>::f
. Таким образом, вы получаете лучшее из двух миров: «задержанный» поиск, защищающий вас, если у вас могут возникнуть проблемы с зависимыми базовыми классами, а также «немедленный» поиск, который освобождает вас отtypename
иtemplate
.Неизвестные специализации
В коде
D
имяtypename D::questionable_type
не является членом текущего экземпляра. Вместо этого язык помечает его как члена неизвестной специализации . В частности, это всегда бывает , когда вы делаетеDependentTypeName::Foo
илиDependentTypedName->Foo
и либо зависимый тип не ток конкретизации (в этом случае компилятор может отказаться и сказать : «мы рассмотрим позже , чтоFoo
есть) , или оно является текущей конкретизацией и имя не было найдено ни в нем, ни в его независимых классах, и существуют также зависимые базовые классы.Представьте, что произойдет, если у нас будет функция-член
h
в указанном вышеA
шаблоне классаВ C ++ 03 язык позволял отлавливать эту ошибку, потому что никогда не было правильного способа создания экземпляра
A<T>::h
(независимо от того, какой аргумент вы указалиT
). В C ++ 11 у языка теперь есть дополнительная проверка, чтобы дать больше оснований для компиляторов реализовать это правило. ПосколькуA
не имеет зависимых базовых классов и неA
объявляет ни одного членаquestionable_type
, имя неA<T>::questionable_type
является ни членом текущей реализации, ничлен неизвестной специализации. В этом случае не должно быть способа, чтобы этот код мог корректно компилироваться во время создания экземпляра, поэтому язык запрещает имени, в котором спецификатор является текущим экземпляром, не быть ни членом неизвестной специализации, ни членом текущего экземпляра (однако это нарушение до сих пор не требуется для диагностики).Примеры и мелочи
Вы можете попробовать эти знания в этом ответе и посмотреть, имеют ли смысл приведенные выше определения для вас на примере из реальной жизни (они повторяются чуть менее подробно в этом ответе).
Правила C ++ 11 делают следующий действительный код C ++ 03 неправильно сформированным (который не был задуман комитетом C ++, но, вероятно, не будет исправлен)
Этот действительный код C ++ 03 связывался
this->f
быA::f
во время создания экземпляра, и все в порядке. Однако C ++ 11 немедленно связывает егоB::f
и требует двойной проверки при создании экземпляра, проверяя, совпадает ли поиск. Однако приC<A>::g
создании экземпляра применяется правило доминирования, иA::f
вместо него выполняется поиск .источник
Какова цель
typename
иtemplate
?typename
иtemplate
могут быть использованы в обстоятельствах, отличных от объявления шаблона.В C ++ существуют определенные контексты, в которых компилятору нужно явно указывать, как обрабатывать имя, и все эти контексты имеют одну общую черту; они зависят как минимум от одного шаблона-параметра .
Мы ссылаемся на такие имена, где может быть неоднозначность в интерпретации, как; « зависимые имена ».
В этом посте будет дано объяснение связи между именами зависимых и двумя ключевыми словами.
СНИПЕТТ ГОВОРИТ БОЛЕЕ 1000 СЛОВ
Попытайтесь объяснить, что происходит в следующем шаблоне функции , для себя, друга или, возможно, вашей кошки; что происходит в выражении ( A )?
Это может быть не так просто, как кажется, более конкретно, результат оценки ( A ) сильно зависит от определения типа, переданного как параметр-шаблон
T
.Различные
T
s могут кардинально изменить семантику.Два разных сценария :
Если мы создадим экземпляр функции-шаблона с типом X , как в ( C ), у нас будет объявление указателя на int с именем x , но;
если мы создадим шаблон с типом Y , как в ( D ), вместо этого ( A ) будет состоять из выражения, которое вычисляет произведение 123, умноженное на некоторую уже объявленную переменную x .
ОБОСНОВАНИЕ
Стандарт C ++ заботится о нашей безопасности и благополучии, по крайней мере, в этом случае.
Чтобы предотвратить потенциальную реализацию реализации неприятных сюрпризов, Стандарт обязывает нас разобраться в неоднозначности зависимого имени , явно указав намерение везде, где мы хотим, чтобы имя воспринималось либо как имя типа , либо как шаблон. идентификатор .
Если ничего не указано, зависимое имя будет считаться либо переменной, либо функцией.
КАК РАБОТАТЬ С ЗАВИСИМЫМИ ИМЕНАМИ ?
Если бы это был голливудский фильм, зависимые имена были бы болезнью, которая распространяется через телесный контакт, мгновенно воздействуя на своего хозяина, чтобы запутать его. Путаница, которая может, возможно, привести к плохо сформированной персо-, эээ ... программе.
Имя-зависимое это любое имя , которое прямо или косвенно зависит от шаблона-параметра .
У нас есть четыре зависимых имени в приведенном выше фрагменте:
SomeTrait<T>
, которые включаютT
и;SomeTrait<T>
, и;SomeTrait<T>
, и;SomeTrait<T>
.Ни одно из утверждений ( E ), ( F ) или ( G ) недопустимо, если компилятор интерпретирует зависимые имена как переменные / функции (что, как было сказано ранее, происходит, если мы явно не говорим иначе).
РЕШЕНИЕ
Чтобы
g_tmpl
иметь правильное определение, мы должны явно указать компилятору, что мы ожидаем тип в ( E ), идентификатор шаблона и тип в ( F ), а также идентификатор шаблона в ( G ).Каждый раз, когда имя обозначает тип, все задействованные имена должны быть либо именами типов, либо пространствами имен , с учетом этого довольно легко увидеть, что мы применяем
typename
в начале нашего полностью определенного имени .template
однако в этом отношении все по-другому, поскольку нет возможности прийти к такому заключению, как; «О, это шаблон, тогда эта другая вещь также должна быть шаблоном» . Это означает, что мы применяемtemplate
непосредственно перед любым именем, которое мы хотели бы рассматривать как таковое.Могу ли я просто вставить ключевые слова перед любым именем?
Правила Стандарта гласят, что вы можете применять ключевые слова до тех пор, пока вы имеете дело с квалифицированным именем ( K ), но если имя не является квалифицированным, приложение неправильно сформировано ( L ).
Примечание : применение
typename
илиtemplate
в контексте, где это не требуется, не считается хорошей практикой; только то, что ты можешь что-то сделать, не значит, что ты должен.Кроме того , существуют контексты , где
typename
иtemplate
будут явно запрещенные:При указании оснований, которые наследует класс
Каждое имя, записанное в списке базовых спецификаторов производного класса, уже обрабатывается как имя типа, поскольку явное указание
typename
является как некорректным, так и избыточным.Когда идентификатор шаблона упоминается в директиве using производного класса
источник
Однако я не уверен, что вы правильно внедрили inUnion. Если я правильно понимаю, этот класс не должен создаваться, поэтому вкладка «сбой» никогда не приведет к сбою. Возможно, было бы лучше указать, находится ли тип в объединении или нет, с простым логическим значением.
PS: Посмотрите на Boost :: Variant
PS2: взгляните на списки типов , особенно в книге Андрея Александреску: современный дизайн C ++
источник
Этот ответ должен быть довольно коротким и приятным, чтобы ответить (частично) на названный вопрос. Если вы хотите получить более подробный ответ, объясняющий, почему вы должны поместить его туда, перейдите сюда .
Общее правило для размещения
typename
ключевого слова в основном, когда вы используете параметр шаблона и хотите получить доступ к вложенномуtypedef
или с использованием псевдонима, например:Обратите внимание, что это также относится к мета-функциям или вещам, которые также принимают общие параметры шаблона. Однако, если предоставленный параметр шаблона является явным типом, вам не нужно указывать
typename
, например:Общие правила добавления
template
квалификатора в основном аналогичны, за исключением того, что они обычно включают шаблонные функции-члены (статические или иные) структуры / класса, который сам шаблонизирован, например:Учитывая эту структуру и функцию:
Попытка получить доступ
t.get<int>()
изнутри функции приведет к ошибке:Таким образом, в этом контексте вам нужно
template
заранее задать ключевое слово и назвать его так:t.template get<int>()
Таким образом, компилятор будет анализировать это правильно, а не
t.get < int
.источник
Я помещаю превосходный ответ JLBorges на аналогичный дословный вопрос от cplusplus.com, так как это самое краткое объяснение, которое я читал по этому вопросу.
Резюме
Используйте ключевое слово typename только в объявлениях и определениях шаблонов, если у вас есть полное имя, которое относится к типу и зависит от параметра шаблона.
источник