Официально для чего нужен typename?


131

Иногда я видел несколько действительно неразборчивых сообщений об ошибках, выплевывающих gccпри использовании шаблонов ... В частности, у меня были проблемы, когда кажущиеся правильными объявления вызывали очень странные ошибки компиляции, которые волшебным образом исчезали, добавляя typenameключевое слово в начале объявление ... (Например, буквально на прошлой неделе я объявлял два итератора как члены другого шаблонного класса, и мне пришлось это сделать) ...

Что за история typename?


Ответы:


207

Ниже приводится цитата из книги Джозаттиса:

Ключевое слово typenameбыло введено, чтобы указать, что следующий идентификатор является типом. Рассмотрим следующий пример:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Здесь typenameиспользуется для пояснения, что SubTypeэто тип class T. Таким образом, ptrэто указатель на тип T::SubType. Без typename, SubType будет считаться статическим членом. таким образом

T::SubType * ptr

будет умножением значения SubTypeтипа Tна ptr.


2
Отличная книга. Прочтите его один раз, а затем сохраните как справочник, если хотите.
deft_code

1
Проницательный читатель поймет, что выражение умножения не допускается грамматикой для объявления члена. Таким образом, C ++ 20 не требует этого typename(хотя и не всех!).
Дэвис Херринг

Не убедил меня. После того, как шаблон создается, очень четко определяется, что такое подтип T ::
kovarex

36

Сообщение Стэна Липпмана в блоге предлагает: -

Страуструп повторно использовал существующее ключевое слово class, чтобы указать параметр типа, вместо того, чтобы вводить новое ключевое слово, которое, конечно, могло бы сломать существующие программы. Дело не в том, что новое ключевое слово не рассматривалось - просто в том, что оно не считалось необходимым, учитывая его потенциальное нарушение. И до стандарта ISO-C ++ это был единственный способ объявить параметр типа.

Итак, в основном Страуструп повторно использовал ключевое слово класса без введения нового ключевого слова, которое впоследствии изменяется в стандарте по следующим причинам

В приведенном примере

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

грамматика языка неверно интерпретируется T::A *aObj;как арифметическое выражение, поэтому вводится новое ключевое слово, называемоеtypename

typename T::A* a6;

он инструктирует компилятор рассматривать последующий оператор как объявление.

Поскольку ключевое слово было в платежной ведомости, черт возьми, почему бы не исправить путаницу, вызванную первоначальным решением повторно использовать ключевое слово class.

Вот почему у нас есть оба

Вы можете взглянуть на этот пост , он определенно поможет вам, я просто извлек из него все, что мог


Да, но тогда зачем было typenameнужно новое ключевое слово , если можно было использовать существующее ключевое слово classдля той же цели?
Джеспер,

5
@Jesper: Я думаю, что ответ Зенуса сбивает с толку. typenameстало необходимо исправить проблему синтаксического анализа, как описано в ответе Naveen, цитируя Josuttis. (Я не думаю, что вставка classв этом месте сработала бы.) Только после того, как новое ключевое слово было принято для этого случая, оно также было разрешено в объявлениях аргументов шаблона ( или это определения? ), Потому что classвсегда было что-то вводит в заблуждение.
SBI

13

Рассмотрим код

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!
    .
    .

6

В некоторых ситуациях, когда вы ссылаетесь на член так называемого зависимого типа (что означает «зависит от параметра шаблона»), компилятор не всегда может однозначно вывести семантическое значение результирующей конструкции, потому что он не знает, что это за имя (т.е. является ли это именем типа, именем элемента данных или именем чего-то еще). В подобных случаях вы должны устранить неоднозначность ситуации, явно указав компилятору, что имя принадлежит типу, определенному как член этого зависимого типа.

Например

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должна использоваться в объявлениях параметров шаблона.


См. Также Описание ключевого слова typename C ++ для получения дополнительной (исходной) информации.
Atafar 05

5

Секрет заключается в том, что шаблон может быть специализирован для некоторых типов. Это означает, что он также может определять интерфейс совершенно по-разному для нескольких типов. Например, вы можете написать:

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тип выглядит совершенно иначе, чем для других Ts. По общему признанию, это не меняет вид 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это не тип. Чтобы сообщить компилятору, чего вы ожидаете, вам нужно добавить файл typenamebefore. Правильный шаблон выглядит так

template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

Итог: вы должны добавлять typenameраньше всякий раз, когда вы используете вложенный тип шаблона в своих шаблонах. (Конечно, только если параметр шаблона вашего шаблона используется для этого внутреннего шаблона.)


5

Два использования:

  1. В качестве templateключевого слова аргумента (вместо class)
  2. typenameКлючевое слово указывает компилятору , что идентификатор является типом (а не переменной - члена статический)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}

4

Я думаю, что во всех ответах упоминалось, что 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.


2
#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;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.