Официально, что такое typename?
Иногда я видел некоторые действительно не поддающиеся расшифровке сообщения об ошибках, выдаваемые gcc при использовании шаблонов... В частности, у меня были проблемы, когда, казалось бы, правильные объявления вызывали очень странные ошибки компиляции, которые волшебным образом исчезали, добавляя префикс "typename" к начало объявления... (Например, только на прошлой неделе я объявил двух итераторов членами другого шаблонного класса, и мне пришлось это сделать)...
Что за история с typename?
7 ответов
Ниже приводится цитата из книги Йосуттиса:
Ключевое слово typename было введено, чтобы указать, что следующий идентификатор является типом. Рассмотрим следующий пример:
template <class T> Class MyClass { typename T::SubType * ptr; ... };
Здесь typename используется для пояснения того, что SubType является типом класса T. Таким образом, ptr является указателем на тип T::SubType. Без typename SubType будет считаться статическим членом. таким образом
T::SubType * ptr
будет умножение значения SubType типа T с ptr.
Блог Стэна Липпмана предлагает:
Страуструп повторно использовал существующее ключевое слово класса, чтобы указать параметр типа, вместо того, чтобы вводить новое ключевое слово, которое, конечно, могло бы сломать существующие программы. Дело не в том, что новое ключевое слово не рассматривалось - просто оно не считалось необходимым, учитывая его потенциальное нарушение. И до стандарта ISO-C++ это был единственный способ объявить параметр типа.
Таким образом, в основном Stroustrup повторно использовал ключевое слово класса, не вводя новое ключевое слово, которое впоследствии было изменено в стандарте по следующим причинам.
В качестве примера приведен
template <class T>
class Demonstration {
public:
void method() {
T::A *aObj; // oops …
// …
};
неправильная интерпретация грамматики языка T::A *aObj;
как арифметическое выражение, поэтому вводится новое ключевое слово под названием typename
typename T::A* a6;
он указывает компилятору обрабатывать последующее утверждение как объявление.
Так как ключевое слово было в платежной ведомости, черт, почему бы не исправить путаницу, вызванную первоначальным решением повторно использовать ключевое слово class.
Вот почему у нас есть оба
Вы можете взглянуть на этот пост, он определенно поможет вам, я просто извлек из него столько, сколько смог
Рассмотрим код
template<class T> somefunction( T * arg )
{
T::sometype x; // broken
.
.
К сожалению, компилятору не обязательно быть экстрасенсом, и он не знает, будет ли T::sometype в конечном итоге ссылаться на имя типа или статический член T. Итак, каждый использует typename
сказать это:
template<class T> somefunction( T * arg )
{
typename T::sometype x; // works!
.
.
В некоторых ситуациях, когда вы ссылаетесь на член так называемого зависимого типа (что означает "зависит от параметра шаблона"), компилятор не всегда может однозначно определить семантическое значение результирующей конструкции, потому что он не знает, какое имя это (то есть, является ли это имя типа, имя члена данных или имя чего-то еще). В подобных случаях вы должны устранить неоднозначность ситуации, явно указав компилятору, что имя принадлежит типу, определенному как член этого зависимого типа.
Например
template <class T> struct S {
typename T::type i;
};
В этом примере ключевое слово typename
в коде, необходимом для компиляции.
То же самое происходит, когда вы хотите сослаться на член шаблона зависимого типа, то есть на имя, которое обозначает шаблон. Вы также должны помочь компилятору, используя ключевое слово template
, хотя он расположен по-другому
template <class T> struct S {
T::template ptr<int> p;
};
В некоторых случаях может быть необходимо использовать оба
template <class T> struct S {
typename T::template ptr<int>::type i;
};
(если я правильно понял синтаксис).
Конечно, другая роль ключевого слова typename
должен использоваться в объявлениях параметров шаблона.
Секрет заключается в том, что шаблон может быть специализирован для некоторых типов. Это означает, что он также может определять интерфейс совершенно по-разному для нескольких типов. Например, вы можете написать:
template<typename T>
struct test {
typedef T* ptr;
};
template<> // complete specialization
struct test<int> { // for the case T is int
T* ptr;
};
Кто-то может спросить, почему это полезно и действительно: это действительно выглядит бесполезным. Но имейте в виду, что, например, std::vector<bool>
reference
тип выглядит совершенно иначе, чем для других T
s. По общему признанию это не меняет вид reference
от типа к чему-то другому, но тем не менее это может произойти.
Что произойдет, если вы напишите свои собственные шаблоны, используя это test
шаблон. Что-то вроде этого
template<typename T>
void print(T& x) {
test<T>::ptr p = &x;
std::cout << *p << std::endl;
}
кажется, это будет хорошо для вас, потому что вы ожидаете, что test<T>::ptr
это тип. Но компилятор не знает, и на самом деле стандарт даже советует ожидать обратного, test<T>::ptr
не тип Чтобы сообщить компилятору, что вы ожидаете, вы должны добавить typename
до. Правильный шаблон выглядит так
template<typename T>
void print(T& x) {
typename test<T>::ptr p = &x;
std::cout << *p << std::endl;
}
Итог: нужно добавить typename
раньше, когда вы используете вложенный тип шаблона в ваших шаблонах. (Конечно, только если для этого внутреннего шаблона используется параметр шаблона вашего шаблона.)
Два использования:
- Как ключевое слово аргумента шаблона (вместо "класс")
- Ключевое слово typename сообщает компилятору, что идентификатор является типом (а не статической переменной-членом)
template <typename T> class X // [1] { typename T::Y _member; // [2] }
Я думаю, что во всех ответах упоминалось, что typename
ключевое слово, используется в двух разных случаях:
а) При объявлении параметра типа шаблона. например
template<class T> class MyClass{}; // these two cases are
template<typename T> class MyNewClass{}; // exactly the same.
Которая между ними нет разницы и они ТОЧНО одинаковые.
б) Перед использованием имени вложенного зависимого типа для шаблона.
template<class T>
void foo(const T & param)
{
typename T::NestedType * value; // we should use typename here
}
Которые не используют typename
приводит к ошибкам синтаксического анализа / компиляции.
Что я хочу добавить ко второму случаю, как упоминалось в книге Скота Мейерса " Эффективный C++", так это то, что существует исключение использованияtypename
перед именем вложенного зависимого типа. Исключением является то, что если вы используете имя вложенного зависимого типа в качестве базового класса или в списке инициализации членов, вам не следует использоватьtypename
там:
template<class T>
class D : public B<T>::NestedType // No need for typename here
{
public:
D(std::string str) : B<T>::NestedType(str) // No need for typename here
{
typename B<T>::AnotherNestedType * x; // typename is needed here
}
}
Примечание: использованиеtypename
для второго случая (т.е. перед именем вложенного зависимого типа) не требуется, начиная с C++20.
#include <iostream>
class A {
public:
typedef int my_t;
};
template <class T>
class B {
public:
// T::my_t *ptr; // It will produce compilation error
typename T::my_t *ptr; // It will output 5
};
int main() {
B<A> b;
int my_int = 5;
b.ptr = &my_int;
std::cout << *b.ptr;
std::cin.ignore();
return 0;
}