Где и почему я должен поместить ключевые слова "template" и "typename"?
в шаблонах, где и почему нужно ставить 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>
имя типа, а не функция?
6 ответов:
чтобы проанализировать программу на 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
только перед полными именами - поэтому считается само собой разумеющимся, что неквалифицированные имена всегда относятся к типам, если они это делают.аналогичная gotcha существует для имен, обозначающих шаблоны, на что намекает вводный текст.
ключевое слово "шаблон"
помните начальную цитату выше и как Стандарт требует специальной обработки и для шаблонов? Давайте возьмем следующий невинный пример:
boost::function< int() > f;
это может показаться очевидным для читателя-человека. Не так для компилятора. Представьте себе следующее произвольное определение
boost::function
иf
:namespace boost { int function = 0; } int main() { int f = 0; boost::function< int() > f; }
это на самом деле действует выражение! Он использует оператор less-than для сравнения
boost::function
от нуля (int()
), а затем использует оператор greater-than для сравнения в результатеbool
противf
. Однако как вы хорошо знаете,boost::function
в реальной жизни - это шаблон, поэтому компилятор знает (14.2/3):после поиска имени (3.4) находит, что имя является именем шаблона, если за этим именем следует
теперь мы вернулись к той же проблеме, что и с
typename
. Что делать, если мы еще не знаем, является ли имя шаблоном при разборе кода? Нам нужно будет вставитьtemplate
непосредственно перед именем шаблона, как указано14.2/4
. Это выглядит так:t::template f<int>(); // call a function template
имена шаблонов не могут появляться только после
::
, но и после->
или.
в доступе к члену класса. Вам также нужно вставить ключевое слово:this->template f<int>(); // call a function template
зависимости
для людей, которые имеют толстые стандартные книги у них на полке и вот хотите знать, о чем именно я говорил, я немного расскажу о том, как это указано в стандарте.
в объявлениях шаблонов некоторые конструкции имеют разные значения в зависимости от того, какие аргументы шаблона вы используете для создания экземпляра шаблона: выражения могут иметь разные типы или значения, переменные могут иметь разные типы или вызовы функций могут в конечном итоге вызывать разные функции. Такие конструкции, как правило, говорят зависит параметры шаблона.
стандарт точно определяет правила, является ли конструкция зависимой или нет. Он разделяет их на логически различные группы: одна ловит типы, другая ловит выражения. Выражения могут зависеть от их значения и/или типа. Итак, мы имеем, с типичными примерами прилагается:
- зависимые типы (например: параметр шаблона типа
T
)- выражения, зависящие от значения (например: a параметр шаблона без типа
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 };
C++11
в то время как правила в C++03 о том, когда вам нужно
typename
иtemplate
в значительной степени разумны, есть один раздражающий недостаток его формулировкиtemplate<typename T> struct A { typedef int result_type; void f() { // error, "this" is dependent, "template" keyword needed this->g<float>(); // OK g<float>(); // error, "A<T>" is dependent, "typename" keyword needed A<T>::result_type n1; // OK result_type n2; } template<typename U> void g(); };
как видно, нам нужно ключевое слово disambiguation, даже если компилятор может прекрасно понять, что
A::result_type
может быть толькоint
(и, следовательно, тип), иthis->g
может быть только шаблон-членовg
объявлена позже (даже еслиA
is явно специализированный где-то, что не повлияет на код в этом шаблоне, поэтому его значение не может быть затронуто более поздней специализациейA
!).текущего экземпляра
чтобы улучшить ситуацию, в C++11 язык отслеживает, когда тип ссылается на вложенный шаблон. Чтобы знать это, тип должен был быть сформирован с использованием определенной формы имени, которая является его собственным именем (в приведенном выше,
A
,A<T>
,::A<T>
). Тип, на который ссылается такое имя, как известно,текущего экземпляра. Может быть несколько типов, которые являются всеми текущими экземплярами, если тип, из которого формируется имя, является членом / вложенным классом (тогдаA::NestedClass
иA
как текущего воплощения).основываясь на этом понятии, язык говорит, что
CurrentInstantiation::Foo
,Foo
иCurrentInstantiationTyped->Foo
(например,A *a = this; a->Foo
) все член текущего экземпляраесли они найдены, чтобы быть члены класса, который является текущим экземпляром или одним из его независимых базовых классов (просто выполнив поиск имени сразу).ключевые слова
typename
иtemplate
теперь не требуется, если квалификатор является членом текущего экземпляра. Ключевой момент здесь, чтобы помнить, чтоA<T>
и еще имя, зависящее от типа (в конце концовT
также зависит от типа). НоA<T>::result_type
как известно, тип - компилятор будет "волшебно" выглядит в этот вид зависимых типов, чтобы понять это.struct B { typedef int result_type; }; template<typename T> struct C { }; // could be specialized! template<typename T> struct D : B, C<T> { void f() { // OK, member of current instantiation! // A::result_type is not dependent: int D::result_type r1; // error, not a member of the current instantiation D::questionable_type r2; // OK for now - relying on C<T> to provide it // But not a member of the current instantiation typename D::questionable_type r3; } };
это впечатляет, но можем ли мы сделать лучше? Язык даже идет дальше и требует что реализация снова смотрит вверх
D::result_type
при созданииD::f
(даже если он нашел свое значение уже во время определения). Когда теперь результат поиска отличается или приводит к неоднозначности, программа плохо сформирована и должна быть дана диагностика. Представьте себе, что произойдет, если мы определилиC
как этоtemplate<> struct C<int> { typedef bool result_type; typedef int questionable_type; };
компилятор требуется, чтобы поймать ошибку при создании экземпляра
D<int>::f
. Таким образом, вы получаете лучшее из двух миров: "отложенный" поиск, защищающий вас, если вы можете попасть в беду с зависимыми базовыми классами, а также "немедленный" поиск, который освобождает вас отtypename
иtemplate
.неизвестные специализации
в коде
D
имяtypename D::questionable_type
не является членом текущего экземпляра. Вместо этого язык отмечает его как a членом неизвестной специализации. В частности, это всегда так, когда вы делаетеDependentTypeName::Foo
илиDependentTypedName->Foo
и либо зависимый тип не текущий экземпляр (в этом случае компилятор может отказаться и сказать: "мы посмотрим позже, чтоFoo
) и его и текущий экземпляр и имя не были найдены в нем или его зависимых базовых классах, а также зависимые базовые классы.представьте себе что произойдет, если у нас есть функция-член
h
в указанные вышеA
шаблон классаvoid h() { typename A<T>::questionable_type x; }
в C++03 язык позволил поймать эту ошибку, потому что не может быть допустимого способа создания экземпляра
A<T>::h
(любой аргумент, который вы даетеT
). В C++11 язык теперь имеет дополнительную проверку, чтобы дать больше оснований для компиляторов реализовать это правило. Так какA
не имеет зависимых базовых классов, аA
не объявляет ни одного членаquestionable_type
имяA<T>::questionable_type
и ни член текущего экземпляра , ни членом неизвестной специализации. В этом случае не должно быть никакого способа, чтобы этот код мог корректно компилироваться во время создания экземпляра, поэтому язык запрещает имя, где квалификатор является текущим экземпляром, не быть ни членом неизвестной специализации, ни членом текущего экземпляра (однако это нарушение по-прежнему не требуется диагностированный.)примеры и мелочи
вы можете попробовать это знание на ответ и посмотрите, имеют ли вышеуказанные определения смысл для вас на примере реального мира (они повторяются немного менее подробно в этом ответе).
правила C++11 делают следующий допустимый код C++03 плохо сформированным (который не был предназначен комитетом C++, но, вероятно, не будет исправлен)
struct B { void f(); }; struct A : virtual B { void f(); }; template<typename T> struct C : virtual B, T { void g() { this->f(); } }; int main() { C<A> c; c.g(); }
этот допустимый код C++03 будет привязан
this->f
доA::f
в момент создания экземпляра и все в порядке. Однако C++11 немедленно связывает его сB::f
и требует двойной проверки при создании экземпляра, проверяя, соответствует ли поиск по-прежнему. Однако при созданииC<A>::g
на Правило Доминирования применяется и поиск будет найтиA::f
вместо.
предисловие
этот пост должен быть легко читаемый альтернатива litb это!--42-->.
основная цель та же; объяснение "когда?- и почему же?"
typename
иtemplate
должны быть применены.
какова цель
typename
иtemplate
?
typename
иtemplate
могут использоваться в обстоятельствах, отличных от объявления шаблон.есть определенные контексты в C++ где компилятору должно быть явно сказано, как обрабатывать имя, и все эти контексты имеют одну общую черту; они зависят по крайней мере от одного шаблон-параметр.
мы ссылаемся на такие названия, где может быть двусмысленность в толковании, как;"зависимые имена".
этот пост будет предлагать объяснение отношений между зависимые-имен, и два ключевых слова.
ФРАГМЕНТ ГОВОРИТ БОЛЕЕ 1000 СЛОВ
попробуйте объяснить, что происходит в следующем шаблон-параметр.
template<class T> void g_tmpl () { SomeTrait<T>::type foo; // (E), ill-formed SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed foo.data<int> (); // (G), ill-formed }
у нас есть четыре зависимая имена в приведенном выше фрагменте кода:
- E)
- "типа" зависит от экземпляра
SomeTrait<T>
, включаяT
, и;- F)
- "NestedTrait", который является template-id зависит от
SomeTrait<T>
и- "типа" в конце (F) зависит NestedTrait, от которой зависит
SomeTrait<T>
и- G)
- "сведения", который выглядит как функции-члена шаблона, косвенно a зависимый-именем С типом фу зависит от экземпляра
SomeTrait<T>
.ни одно из утверждений (E), (F) или (G) допустимо, если компилятор интерпретирует зависимые-имен как переменные / функции (что, как было указано ранее, происходит, если мы явно не говорим иначе.)
РЕШЕНИЕ
сделать
g_tmpl
есть допустимое определение мы должны явно сказать компилятору, что мы ожидаем тип в (E), а template-id и тип in (F), и template-id in (G).template<class T> void g_tmpl () { typename SomeTrait<T>::type foo; // (G), legal typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal foo.template data<int> (); // (I), legal }
каждый раз a имя обозначает тип, все имена должна быть type-names или пространства имен, имея это в виду, это довольно легко увидеть, что мы применяем
typename
в начале нашего полностью доменное имя.
template
однако, отличается в этом отношении, так как нет никакого способа прийти к такому выводу, как; "Ах, это шаблон, чем эта другая вещь также должна быть шаблоном". Это означает, что мы применяемtemplate
прямо в перед любым имя что мы хотели бы рассматривать как таковой.
МОГУ Я ПРОСТО ВСТАВИТЬ ключевые слова ПЕРЕД ЛЮБЫМ ИМЕНЕМ?
"могу я просто придерживаться
typename
иtemplate
перед любым именем? Я не хочу беспокоиться о контексте, в котором они появляются..." -Some C++ Developer
правила в стандарте гласит, что вы можете применять ключевые слова до тех пор, как вы имея дело с квалифицированное-имя (K), но если фамилия не квалификации приложение плохо сформировано (L).
namespace N { template<class T> struct X { }; }
N:: X<int> a; // ... legal typename N::template X<int> b; // (K), legal typename template X<int> c; // (L), ill-formed
Примечание применение
typename
илиtemplate
в контексте, где это не требуется, не считается хорошей практикой; просто потому, что вы можете что-то сделать, не означает, что вы должен.
кроме того, есть контексты, где
typename
иtemplate
are явно запрещены:
при указании оснований, из которых класс наследует
каждое имя, написанное в производном классе base-specifier-list уже рассматривается как type-name, явно указать
typename
и плохо сформировано, и избыточный.// .------- the base-specifier-list template<class T> // v struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ { ... };
когда template-id это тот, на который ссылаются в производном классе using-directive
struct Base { template<class T> struct type { }; }; struct Derived : Base { using Base::template type; // ill-formed using Base::type; // legal };
typedef typename Tail::inUnion<U> dummy;
однако, я не уверен, что вы реализация inUnion правильно. Если я правильно понимаю, этот класс не должен быть создан, поэтому вкладка "fail" никогда не будет автоматически отказывать. Возможно, было бы лучше указать, находится ли тип в объединении или нет с простым логическим значением.
template <typename T, typename TypeList> struct Contains; template <typename T, typename Head, typename Tail> struct Contains<T, UnionNode<Head, Tail> > { enum { result = Contains<T, Tail>::result }; }; template <typename T, typename Tail> struct Contains<T, UnionNode<T, Tail> > { enum { result = true }; }; template <typename T> struct Contains<T, void> { enum { result = false }; };
PS: взгляните на Boost:: Variant
PS2: взгляните на typelists, особенно у Андрея Александреску книги: Современное проектирование на С++
этот ответ должен быть довольно коротким и сладким, чтобы ответить (часть) на титульный вопрос. Если вы хотите получить ответ с более подробной информацией, которая объясняет, почему вы должны поместить их туда, пожалуйста, перейдите здесь.
общее правило для нанесения
typename
ключевое слово в основном, когда вы используете параметр шаблона, и вы хотите, чтобы открыть вложенныйtypedef
или через-псевдоним, например:template<typename T> struct test { using type = T; // no typename required using underlying_type = typename T::type // typename required };
Примечание. это также относится к мета-функциям или вещам, которые также принимают общие параметры шаблона. Однако, если предоставленный параметр шаблона является явным типом, то вам не нужно указывать
typename
, например:template<typename T> struct test { // typename required using type = typename std::conditional<true, const T&, T&&>::type; // no typename required using integer = std::conditional<true, int, float>::type; };
общие правила добавления
template
квалификаторы в основном похожи, за исключением того, что они обычно включают шаблонные функции-члены (статические или другие) структуры / класса, которые сами шаблонны, например:учитывая эту структуру и функция:
template<typename T> struct test { template<typename U> void get() const { std::cout << "get\n"; } }; template<typename T> void func(const test<T>& t) { t.get<int>(); // error }
попытка доступа
t.get<int>()
изнутри функция приведет к ошибке:main.cpp:13:11: error: expected primary-expression before 'int' t.get<int>(); ^ main.cpp:13:11: error: expected ';' before 'int'
таким образом, в этом контексте вам понадобится
template
ключевое слово заранее и назовите его так:
t.template get<int>()
таким образом, компилятор будет обрабатывать это правильно, а не
t.get < int
.
Я ставлю jlborges отлично ответ к аналогичному вопросу дословно из cplusplus.com, так как это самое краткое объяснение, которое я читал на эту тему.
в шаблоне, который мы пишем, есть два типа имен, которые могут быть использованы - зависимые имена и не зависимые имена. Зависимое имя-это имя, которое зависит от параметра шаблона; независимо от того, что такое параметры шаблона, имя, не зависящее от него, имеет одно и то же значение.
например:
template< typename T > void foo( T& x, std::string str, int count ) { // these names are looked up during the second phase // when foo is instantiated and the type T is known x.size(); // dependant name (non-type) T::instance_count ; // dependant name (non-type) typename T::iterator i ; // dependant name (type) // during the first phase, // T::instance_count is treated as a non-type (this is the default) // the typename keyword specifies that T::iterator is to be treated as a type. // these names are looked up during the first phase std::string::size_type s ; // non-dependant name (type) std::string::npos ; // non-dependant name (non-type) str.empty() ; // non-dependant name (non-type) count ; // non-dependant name (non-type) }
то, к чему относится зависимое имя, может быть чем-то другим для каждого отдельного экземпляра шаблона. Как следствие, шаблоны C++ подвергаются "двухфазному поиску имен". При первоначальном разборе шаблона (до создания любого экземпляра) компилятор ищет несвязанные имена. Когда происходит конкретный экземпляр шаблона, параметры шаблона известны к тому времени, и компилятор выглядит до зависимых имен.
на первом этапе синтаксический анализатор должен знать, является ли зависимое имя именем типа или именем не-типа. По умолчанию, зависимым именем считается имя не-типа. Ключевое слово typename перед зависимым именем указывает, что это имя типа.
резюме
используйте ключевое слово typename только в объявлениях и определениях шаблонов, если у вас есть квалифицированный имя, которое относится к типу и зависит от параметра шаблона.