Де і чому я повинен розміщувати ключові слова "шаблон" та "ім'я типу"?


1125

У шаблонах, де і чому я повинен ставити 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>називає тип, а не функцію?


1
Набридливе питання: чому б не підвищити :: Варіант?
Ассаф Лав'є

58
Політична чутливість, мобільність.
MSalters

5
Я зробив ваше власне запитання ("Куди поставити шаблон / ім'я?") Краще виділитися, поставивши на початку остаточне запитання та код і скоротивши код горизонтально, щоб він міг відповідати екрану 1024x.
Йоханнес Шауб - ліб

7
Видалено "залежні імена" з назви, оскільки, здається, більшість людей, які задаються питанням про "ім'я типу" та "шаблон", не знають, що таке "залежні імена". Це має бути менш заплутаним для них таким чином.
Йоханнес Шауб - ліб

2
@MSalters: прискорення є досить портативним. Я б сказав, що лише політика є загальною причиною того, що стимул часто не враховується. Єдиною вагомою причиною, яку я знаю, є збільшені терміни складання. В іншому випадку мова йде про те, щоб втратити тисячі доларів на винахід колеса.
v.oddou

Відповіді:


1161

(Дивіться також мою відповідь на C ++ 11 )

Щоб проаналізувати програму C ++, компілятору необхідно знати, чи є певні імена типи чи ні. Наступний приклад демонструє, що:

t * f;

Як це слід розбирати? Для багатьох мов компілятору не потрібно знати значення імені, щоб проаналізувати і в основному знати, яку дію виконує рядок коду. Однак у С ++ вищезазначене може давати абсолютно різні інтерпретації залежно від tзасобів. Якщо це тип, то це буде оголошення вказівника f. Однак якщо це не тип, це буде множенням. Тож стандарт C ++ говорить у пункті (3/7):

Деякі назви позначають типи або шаблони. Взагалі, щоразу, коли зустрічається ім’я, необхідно визначити, чи позначає це ім'я одну з цих сутностей, перш ніж продовжувати розбирати програму, яка її містить. Процес, який визначає це, називається пошуку імені.

Як компілятор дізнається, на яке ім'я t::xпосилається, якщо tстосується параметра типу шаблону? xможе бути статичним членом int даних, який може бути множений або в рівній мірі може бути вкладеним класом або typedef, який може дати заяву. Якщо ім'я має це властивість - його неможливо шукати до тих пір, поки не будуть відомі фактичні аргументи шаблону - тоді воно називається залежним ім'ям (воно "залежить" від параметрів шаблону).

Ви можете порекомендувати просто зачекати, поки користувач інстанціює шаблон:

Зачекаємо, поки користувач не буде ідентифікувати шаблон, а потім пізніше з’ясуємо справжнє значення t::x * f;.

Це буде працювати і фактично дозволено Стандартом як можливий підхід до впровадження. Ці компілятори в основному копіюють текст шаблону у внутрішній буфер, і лише тоді, коли потрібна інстанція, вони аналізують шаблон і, можливо, виявляють помилки у визначенні. Але замість того, щоб заважати користувачам шаблону (погані колеги!) Помилками, зробленими автором шаблону, інші реалізації вирішують перевірити шаблони на початку та ввести помилки у визначенні якнайшвидше, перш ніж з'явиться інстанція.

Тому повинен бути спосіб сказати компілятору, що певні імена є типами, а певні імена - ні.

Ключове слово "typename"

Відповідь: Ми вирішуємо, як компілятор повинен проаналізувати це. Якщо t::xце залежне ім'я, тоді нам потрібно зробити його префіксом, typenameщоб сказати компілятору проаналізувати його певним чином. Стандарт говорить (14,6 / 2):

Ім'я, яке використовується в оголошенні або визначенні шаблону, і яке залежить від параметра-шаблону, передбачається не називати тип, якщо відповідний пошук імені не знайде ім'я типу або ім'я не кваліфікується за ключовим словом.

Існує багато імен, для яких 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лише перед кваліфікованими іменами , тому вважається, що некваліфіковані імена завжди відомі для посилань на типи, якщо вони так роблять.

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

Ключове слово "шаблон"

Пам’ятайте початкову цитату вище і як Стандарт вимагає спеціального поводження з шаблонами? Візьмемо такий приклад невинного вигляду:

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також потрібно шукати в контексті інстанції, це також має бути залежною назвою (на щастя, з середини С ++ 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
     };

22
Ця відповідь була скопійована з моєї попередньої статті FAQ, яку я видалив, тому що я виявив, що мені краще використовувати існуючі подібні запитання, а не створювати нові "псевдо питання" лише для того, щоб відповісти на них. Дякуємо, перейдіть до @Prasoon , який відредагував ідеї останньої частини (випадки, коли ім'я / шаблон заборонено) у відповідь.
Йоханнес Шауб - ліб

1
Чи можете ви мені допомогти, коли я повинен використовувати цей синтаксис? this-> шаблон f <int> (); Я розумію, що ця помилка "шаблон" (як розбірник) дозволена лише в шаблонах, але без ключового слова шаблону вона працює чудово.
балки

1
Я задав подібне запитання сьогодні, яке незабаром було позначено як дублікат: stackoverflow.com/questions/27923722/… . Мені доручили відродити це питання, а не створювати нове. Треба сказати, що я не згоден, щоб вони були дублікатами, але хто я, правда? Отже, чи є якась причина, яка typenameзастосовується навіть тоді, коли синтаксис не дозволяє альтернативних інтерпретацій, крім імен типів, в цей момент?
JorenHeit

1
@Pablo ви нічого не пропускаєте. Але все ж вимагається писати розбіжність, навіть якщо повний рядок вже не буде неоднозначним.
Йоханнес Шауб - ліб

1
@Pablo має на меті спростити мову та компілятори. Є пропозиції, що дозволяють більшій кількості ситуацій автоматично з'ясовувати речі, так що ключове слово потрібно рідше. Зверніть увагу , що в вашому прикладі, маркер є неоднозначним , і тільки після того, як ви бачили «>» після того, як подвійний, ви можете неоднозначність його як кут шаблону кронштейн. Для подальшої інформації я не запитую неправильну особу, оскільки у мене немає досвіду впровадження аналізатора компіляторів C ++.
Йоханнес Шауб - ліб

135

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

Уявіть, що станеться, якби у нас була функція члена 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замість цього.


fyi - на цю відповідь посилається тут: stackoverflow.com/questions/56411114/… Значна частина коду у цій відповіді не складається в різних компіляторах.
Адам Ракіс

@AdamRackis припускаючи, що специфікація C ++ не змінилася, змінилася з 2013 року (дата, коли я написав цю відповідь), тоді компілятори, з якими ви спробували свій код, просто ще не реалізуєте цю C ++ 11 + -функцію.
Йоханнес Шауб - ліб

98

ПЕРЕДМОВА

Ця публікація призначена як легка для читання альтернатива публікації litb .

Основна мета однакова; пояснення до "Коли?" і чому?" typenameі templateповинні бути застосовані.


Яка мета typenameі template?

typenameі templateвони можуть бути використані за інших обставин, ніж декларування шаблону.

У C ++ є певні контексти, де компілятору потрібно чітко сказати, як поводитися з іменем, і всі ці контексти мають одне спільне; вони залежать принаймні від одного шаблону-параметра .

Ми посилаємось на такі назви, де може бути неоднозначність у тлумаченні, як; " залежні імена ".

Ця публікація запропонує пояснення взаємозв'язку між залежними іменами та двома ключовими словами.


SNIPPET МОЖЕТЕ БЕЗ 1000 СЛОВІВ

Спробуйте пояснити, що відбувається в наступному шаблоні функцій , будь-якому собі, другові або, можливо, вашій кішці; що відбувається у висловленні, позначеному ( А )?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Це може бути не так просто, як можна думати, точніше результат оцінки ( A ) сильно залежить від визначення типу, переданого як шаблон-параметр T.

Різні Ts можуть різко змінити семантику, що займається.

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 ++ піклується про нашу безпеку та добробут, принаймні в цьому випадку.

Щоб уникнути того, що впровадження потенційно може зазнати неприємних сюрпризів, Стандарт вимагає, щоб ми розбирали неоднозначність імені залежного , чітко заявляючи про наміри в будь-якому місці, де ми хотіли б ставитись до цього імені як до імені типу , або до шаблону - ід .

Якщо нічого не вказано, ім'я залежного буде вважатися або змінною, або функцією.



ЯК ВЗАЄМО ВІДПОВІДАЛЬНІ ІМЕНИ ?

Якби це голлівудський фільм, імена залежних були б хворобою, яка поширюється контактом з тілом, миттєво вражає свого господаря, щоб заплутатися. Плутанина, яка, можливо, може призвести до неправильно сформованої програми perso-, erhm ...

Ім'я-залежне це будь-який ім'я , яке прямо або побічно залежить від шаблону-параметра .

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>і;
  • Г )
    • "data" , схожий на шаблон функції-члена , є опосередковано- залежною назвою, оскільки тип foo залежить від інстанції SomeTrait<T>.

Жодне з висловлювань ( 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 */ {
       ...
     };


  • Коли ідентифікатор шаблону є тим, на який посилається у похідному класі -use-директиві

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };

20
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 ++


inUnion <U> буде створений, якщо ви, наприклад, намагалися викликати Union <float, bool> :: operator = (U) з U == int. Він викликає приватний набір (U, inUnion <U> * = 0).
MSalters

А робота з результатом = true / false полягає в тому, що мені потрібно boost :: enable_if <>, що несумісне з нашою поточною ланцюжком інструментів OSX. Окремий шаблон все ще є хорошою ідеєю.
MSalters

Luc означає манекен typedef Tail :: inUnion <U>; рядок. це дозволить створити Хвіст. але не в об'єднанні <U>. він отримує примірник, коли йому потрібно повне його визначення. це відбувається, наприклад, якщо ви берете розмірofof або звертаєтесь до члена (використовуючи :: foo). @MSalters все одно у вас є ще одна проблема:
Йоганнес Шауб - ліб

-sizeof (U) ніколи не є негативним :), оскільки size_t - це цілочислений без підпису. ви отримаєте дуже високу кількість. ви, ймовірно, хочете зробити sizeof (U)> = 1? -1: 1 або подібне :)
Йоханнес Шауб - ліб

Я б просто залишив його невизначеним і оголосив би лише його: template <typename U> struct inUnion; так що це, безумовно, не можна створити. я думаю, маючи його з sizeof, компілятору дозволено також надати вам помилку, навіть якщо ви її не екземпляру, оскільки якщо знає, що розмір (U) завжди> = 1 і ...
Йоханнес Шауб - litb

20

Ця відповідь має бути досить короткою і милою, щоб відповісти (частина) на заголовке питання. Якщо ви хочете відповісти з більш детальною інформацією, яка пояснює, чому ви повинні їх помістити туди, будь ласка, перейдіть сюди .


Загальне правило для введення 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.


2

Я розміщую чудову відповідь 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 ++ підлягають "двофазному пошуку імен". Коли шаблон спочатку аналізується (до того, як відбудеться будь-яка інстанція), компілятор шукає незалежні імена. Коли відбувається певна інстанціалізація шаблону, параметри шаблона відомі до того часу, і компілятор шукає залежні імена.

Під час першої фази аналізатору необхідно знати, чи залежне ім'я - це ім'я типу чи ім'я нетипу. За замовчуванням залежне ім'я вважається іменем нетипового типу. Ключове слово name type перед залежною назвою вказує, що це ім'я типу.


Підсумок

Використовуйте ім'я типу ключового слова лише в деклараціях шаблонів та визначеннях, якщо у вас є кваліфіковане ім’я, яке посилається на тип і залежить від параметра шаблону.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.