Где и почему я должен поставить ключевые слова "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>
называет тип, а не функцию?
10 ответов
Чтобы проанализировать программу на 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) обнаружит, что имя является шаблоном-именем, если за этим именем следует символ <, то <всегда берется как начало списка аргументов шаблона, а не как имя, за которым следует меньше чем оператор.
Теперь мы вернулись к той же проблеме, что и с 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 };
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();
};
Как видно, нам нужно ключевое слово устранения неоднозначности, даже если компилятор может сам 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
больше не требуются, если классификатор является членом текущего экземпляра. Ключевой момент, о котором следует помнить: 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
не является членом текущего экземпляра. Вместо этого язык помечает его как члена неизвестной специализации. В частности, это всегда тот случай, когда вы делаете DependentTypeName::Foo
или же DependentTypedName->Foo
и либо зависимый тип не является текущей реализацией (в этом случае компилятор может сдаться и сказать "мы посмотрим позже, что Foo
is) или это текущий экземпляр, имя которого не найдено ни в нем, ни в его независимых классах, а также есть зависимые базовые классы.
Представьте, что произойдет, если у нас будет функция-член 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
вместо.
ПРЕДИСЛОВИЕ
Этот пост предназначен для удобной для чтения альтернативы сообщению Литба.
Основная цель та же; объяснение "Когда?" и почему?"
typename
а такжеtemplate
должны быть применены.
Какова цель typename
а также template
?
typename
а также template
могут быть использованы в обстоятельствах, отличных от объявления шаблона.
В C++ существуют определенные контексты, в которых компилятору нужно явно указывать, как обрабатывать имя, и все эти контексты имеют одну общую черту; они зависят как минимум от одного шаблона-параметра.
Мы ссылаемся на такие имена, где может быть неоднозначность в интерпретации, как; " зависимые имена ".
В этом посте будет дано объяснение связи между именами-зависимостями и двумя ключевыми словами.
СНИПЕТТ ГОВОРИТ БОЛЕЕ 1000 СЛОВ
Попытайтесь объяснить, что происходит в следующем шаблоне функции, для себя, друга или, возможно, вашей кошки; что происходит в утверждении с пометкой (A)?
template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }
Это может быть не так просто, как кажется, более конкретно, результат оценки (A) сильно зависит от определения типа, переданного как template-параметр. T
,
Разные T
s может радикально изменить семантику.
struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> ();
struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();
Два разных сценария:
Если мы создадим экземпляр функции-шаблона с типом X, как в (C), у нас будет объявление указателя на int с именем x, но;
если мы создадим шаблон с типом Y, как в (D), вместо этого (A) будет состоять из выражения, которое вычисляет произведение 123, умноженное на некоторую уже объявленную переменную x.
ОБОСНОВАНИЕ
Стандарт C++ заботится о нашей безопасности и благополучии, по крайней мере, в этом случае.
Чтобы предотвратить потенциальную реализацию реализации неприятных сюрпризов, Стандарт обязывает нас разобраться в неоднозначности зависимого имени, явно указав намерение везде, где мы хотим, чтобы имя воспринималось как имя типа или как шаблон. идентификатор
Если ничего не указано, зависимое имя будет считаться либо переменной, либо функцией.
КАК ОБРАЩАТЬСЯ С ЗАВИСИМЫМИ ИМЕНАМИ?
Если бы это был голливудский фильм, зависимые имена были бы болезнью, которая распространяется через телесный контакт, мгновенно воздействуя на своего хозяина, чтобы запутать его. Путаница, которая может, возможно, привести к плохо сформированной персо-, эээ... программе.
Зависимое имя - это любое имя, которое прямо или косвенно зависит от параметра шаблона.
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
}
У нас есть четыре зависимых имени в приведенном выше фрагменте:
- Е)
- "тип" зависит от экземпляра
SomeTrait<T>
, который включает в себяT
, а также;
- "тип" зависит от экземпляра
- F)
- "NestedTrait", который является идентификатором шаблона, зависит от
SomeTrait<T>
, а также; - "тип" в конце (F) зависит от NestedTrait, который зависит от
SomeTrait<T>
, а также;
- "NestedTrait", который является идентификатором шаблона, зависит от
- Г)
- "data", который выглядит как шаблон функции-члена, является косвенно зависимым именем, так как тип foo зависит от реализации
SomeTrait<T>
,
- "data", который выглядит как шаблон функции-члена, является косвенно зависимым именем, так как тип foo зависит от реализации
Ни одно из утверждений (E), (F) или (G) не является допустимым, если компилятор интерпретирует зависимые имена как переменные / функции (что, как было сказано ранее, происходит, если мы явно не говорим иначе).
РЕШЕНИЕ
Делать g_tmpl
Чтобы иметь правильное определение, мы должны явно указать компилятору, что мы ожидаем тип в (E), идентификатор шаблона и тип в (F), а также идентификатор шаблона в (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
}
Каждый раз, когда имя обозначает тип, все задействованные имена должны быть либо именами типов, либо пространствами имен, с учетом этого довольно легко увидеть, что мы применяем 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
явно запрещено:
При указании оснований, которые наследует класс
Каждое имя, записанное в списке базовых спецификаций производного класса, уже рассматривается как имя типа, явно указывающее
typename
и плохо сформирован, и избыточен.// .------- the base-specifier-list template<class T> // v struct Derived : typename SomeTrait<T>::type /* <- ill-formed */ { ... };
Когда идентификатор шаблона - это тот, на который ссылается директива using производного класса
struct Base { template<class T> struct type { }; }; struct Derived : Base { using Base::template type; // ill-formed using Base::type; // legal };
Этот ответ должен быть довольно коротким и приятным, чтобы ответить (частично) на названный вопрос. Если вы хотите получить более подробный ответ, объясняющий, почему вы должны поместить его туда, перейдите сюда.
Общее правило для размещения 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
,
typedef typename Tail::inUnion<U> dummy;
Тем не менее, я не уверен, что ваша реализация inUnion верна. Если я правильно понимаю, этот класс не предназначен для создания экземпляра, поэтому вкладка "сбой" никогда не приведет к сбою. Возможно, было бы лучше указать, находится ли тип в объединении или нет, с простым логическим значением.
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: взгляните на списки типов, особенно в книге Андрея Александреску: современный дизайн C++
C++20 aka C++2a
As outlined in this Proposal, C++20 / C++2a has further relaxed the requirements for the keyword. In particular, may now be omitted in all those places, where syntactically only a type is legal. So, if an unknown expression must be a type, C++20 will actually treat it as a type. For backwards compatibility, may still be used, though.
In particular, most
using
and
typedef
declarations can now be written without . can also be ommited in the declaration of method return types (including trailing return types), in the declaration of method and lambda parameters and in the type argument to
static_cast
,
const_cast
,
dynamic_cast
and
reinterpret_cast
.
One notable exception, where is still required, is in the argument list of instantiations of user or library defined templates: Even, if that particular argument was declared to be a type, the
typename
keyword is still required. So
static_cast<A::B>(arg)
is legal in C++20, but
my_template_class<A::B>(arg)
is ill-formed, if A is a dependant scope and
my_template_class
expects a type.
A few examples:
class A { public: typedef int type; static const int val { 1 }; };
class B { public: typedef float type; static const int val { 2 }; };
template<typename T> class C {};
template<int I> class D {};
template<typename T> class X {
T::type v; // OK
T::type f(T::type arg) { return arg; } // OK
T::type g(double arg) { return static_cast<T::type>(arg); } // OK
// C<T::type> c1; // error
D<T::val> d; // OK (as has always been)
C<typename T::type> c2; // OK (old style)
typedef T::type mytype; // OK
using mytypeagain = T::type; // OK
C<mytype> c3; // OK (via typedef / using)
};
X<A> xa;
X<B> xb;
С++20 и С++23
Для тех, кто заинтересован, я дам полный ответ с точки зрения современного C++. Между C++20 и C++23 есть лишь незначительные различия.
Зачем нам нужно и?
Основная проблема заключается в том, что компилятор не может понять C++ без дополнительных подсказок. Формально грамматика C++ контекстно-зависима, и вам необходимо предоставить дополнительные средства устранения неоднозначности.
int x, y;
template <typename T>
void f() {
T::type * x;
// if 'type' is a type, then this is declaring 'x' as a pointer to 'T::type'
// if 'type' is a static data member, this is multiplying 'x' with 'T::type'
typename T::type* x; // unambiguous
T::templ<0>> y;
// if 'templ' is a template, then this is ( (T::templ<0>) > y) )
// if 'templ' is a static data member, this is ((T::templ < 0) >> y)
T::template templ<0>> y; // unambiguous
}
Как видите, проблема в том, что тип может быть любым, а значит и может быть чем угодно: статические члены данных, псевдонимы типов, шаблоны, шаблоны функций-членов, нешаблонные функции-члены и т. д. Это потому, что иT::templ
являются в зависимости от параметра шаблона.
C++ разрешает эту двусмысленность, рассматривая
T::something
в качестве члена данных или функции-члена, если есть сомнения. Только если известно или если вы укажете или ,something
интерпретируется как тип или шаблон.
Зависимые типы и выражения
Подводя итог терминологии:
- Тип, такой как
T::type
зависит зависимыми типамиот параметра шаблонаT
. - Такое выражение, как зависит от типа, если или зависит от параметра типа шаблона.
- Такое выражение, как
(x + y)
зависит от стоимости, еслиx
илиy
зависят от параметра шаблона, не являющегося типом.
Как правило, это становится необходимым, когда задействованы зависимые типы. Это также может произойти, если зависимый тип формируется из зависимого выражения, напримерdecltype(x + y)::type
.
Как работают ключевые слова и?
Эти ключевые слова иногда называются средствами устранения неоднозначности и сообщают компилятору, что вам нужен тип или шаблон вместо члена данных/функции-члена.
Устранение неоднозначности
добавляется к спецификатору имени типа и применяется к имени, которое требует устранения неоднозначности. Например:
typename T::type // interpreted as type
│ ▲
└────────┘
typename T::type::type // interpreted as type
│ ▲
└──────────────┘
typename T::templ<0>::type // interpreted as non-type
│ ▲
└────────┘
typename T::template templ<0>::type // interpreted as a type
│ ▲
└───────────────────────────┘
Устранение неоднозначности
действует как «клей» внутри квалифицированного идентификатора , что делает его таким<
интерпретируется не как оператор «меньше», а как начало списка .
T::templ<0> // '<' is interpreted as less-than
T::template templ<0> // '<0>' is interpreted as template-argument-list
T::template templ<0>::templ<1> // '<1' is interpreted as less than 1
typename T::templ<0> // '<0>' is interpreted as template-argument-list
│ ▲ // due to type-only context
└────────┘
Как видно из последнего примера, можно сделать ненужным, посколькуtempl<
может интерпретироваться как только в том случае, еслиtempl
должен быть тип.
Существует еще одно (сейчас устаревшее) использование , а именно для предоставления аргументов шаблона-шаблона:
template <template <typename> typename TT>
void f();
template <typename T>
void g() { f<T::template templ>(); }
Такое использование устарело, поскольку всегда ясно, что<...>
в этом контексте является аргументов-шаблонасписком аргументов шаблонасписок аргументов шаблона и бессмысленно.
Каковы послабления в C++20 и C++23?
Многие варианты использования и теперь стали ненужными. Как видно выше, иногда делает это ненужным. В более общем смысле, его можно опустить в контексте только типа .
Например:
using T = T::type; // OK
static_cast<T::type>(...); // OK
std::is_const_v<T::type>; // OK
void f(T::type t); // OK
До появления C++20 эти и другие варианты использования были некорректными. В C++23 добавлено взаимодействие между and, что исключает дальнейшее использование. Соответствующие предложения таковы:
- P0634: Долой !(С++20)
- P1787: Объявления и где их найти (C++23)
В общем, делается попытка исключить все места, где и в чем нет особой необходимости. Обратите внимание, чтоtemplate
и не всегда необходимы для устранения неоднозначности, а просто помогают в анализе. Например:
typename T::a::b::c::d::e::f::g::h::i::j x,
Здесь нам все еще нужно, несмотря на то, что это однозначно декларация. В противном случае, чтобы узнать, чтоa
это тип иa::
действует.
Поэтому маловероятно, чтоtypename
когда-либо станет необязательным в этом контексте.
Зависимое имя - это имя, зависящее от параметров шаблона, нам нужно проинструктировать компилятор, чтобы правильно скомпилировать класс / функцию шаблона, прежде чем фактически инициировать их.
typename -> сообщить компилятору, что зависимое имя является фактическим типом
template <class T> struct DependentType { typename T::type a; using Type=typename T::type; };
шаблон -> сообщить компилятору, что зависимое имя является функцией / классом шаблона
template <class T> struct DependentTemplate { // template function template <class U> static void func() {} // template class template <class U> struct ClassName{}; }; template <class T1, class T2> void foo() { // 3 ways to call a dependent template function DependentTemplate<T1>::template func<T2>(); DependentTemplate<T1>().template func<T2>(); (new DependentTemplate<T1>())->template func<T2>(); // You need both typename and template to reference a dependent template class typename DependentTemplate<T1>::template ClassName<T2> obj; using Type=typename DependentTemplate<T1>::template ClassName<T2>; }
Я помещаю превосходный ответ 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 только в объявлениях и определениях шаблонов, если у вас есть полное имя, которое относится к типу и зависит от параметра шаблона.
Простой
Он понадобится вам, когда вы вызываете шаблонную функцию изнутри шаблонного класса:
#include <iostream>
#include <string>
struct printable {
std::string mystr = "Hello World";
template <typename T>
auto print() {
if constexpr (std::same_as<T, std::string>) {
std::cout << mystr << std::endl;
}
}
};
template <typename Printable>
struct entity {
auto print(Printable& myprintable) {
myprintable.template print<std::string>();
}
};
int main() {
entity<printable> e;
printable p;
e.print(p);
}
Выведет
Hello World
из шаблонаprint()
функционировать вprintable
.