Офіційно, для чого таке ім'я типу?


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

Повідомлення BLog Стен Ліппман пропонує: -

Stroustrup повторно використовував наявне ключове слово, щоб вказати параметр типу, а не вводити нове ключове слово, яке, звичайно, може порушити існуючі програми. Не те, що нове ключове слово не розглядалося - просто те, що воно не вважалося необхідним, враховуючи його потенційний зрив. І аж до стандарту ISO-C ++, це був єдиний спосіб оголосити параметр типу.

Тому в основному Stroustrup повторно використовував ключове слово без введення нового ключового слова, яке згодом змінюється в стандарті з наступних причин

Як наведений приклад

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

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

typename T::A* a6;

він доручає компілятору ставитися до наступного твердження як до декларації.

Оскільки ключове слово було на заробітній платі, чорт, чому б не виправити плутанину, спричинену оригінальним рішенням про повторне використання ключового слова класу.

Ось чому у нас і те, і інше

Ви можете поглянути на цю посаду , вона вам точно допоможе, я просто витягнув з неї стільки, скільки міг


Так, але тоді для чого було потрібно нове ключове слово typename, якщо ви могли використовувати існуюче ключове слово classз тією ж метою?
Jesper

5
@Jesper: Я думаю, що відповідь Ксена тут заплутана. typenameвиникла потреба виправити розбір питання, як описано у відповіді Навена, цитуючи Йосуттіса. (Я не думаю, що вставка в 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повинна бути використана в деклараціях параметрів шаблону.


Дивіться також Опис ключового слова назви типу C ++ для отримання додаткової інформації.
Атафар

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>referenceTreference

Тепер, що станеться, якщо ви пишете власні шаблони за допомогою цього 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раніше, коли ви використовуєте вкладений тип шаблону у своїх шаблонах. (Звичайно, лише якщо параметр шаблону використовується для цього внутрішнього шаблону.)


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.

Яка різниця між ними немає, і вони ТОЧНО однакові.

b) Перед використанням вкладеного імені залежного типу для шаблону.

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.