(Дивіться також мою відповідь на 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
};