Где и почему я должен поставить ключевые слова «template» и «typename»?

913

В шаблонах, где и зачем мне помещать 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 является зависимым именем, а VС++ совершенно прав. Я также знаю, что я должен добавить template где-нибудь, чтобы сообщить компилятору, что inUnion является идентификатором шаблона. Но где именно? И следует ли тогда предположить, что inUnion является шаблоном класса, то есть inUnion<U> называет тип, а не функцию?

  • 1
    Раздражающий вопрос: почему бы не повысить :: Variant?
  • 51
    Политическая чувствительность, мобильность.
Показать ещё 9 комментариев
Теги:
templates
dependent-name
typename
c++-faq

6 ответов

1040
Лучший ответ

Чтобы разобрать программу на С++, компилятор должен знать, являются ли определенные имена типами или нет. Следующий пример демонстрирует, что:

t * f;

Как это следует анализировать? Для многих языков компилятору не нужно знать значение имени для разбора и в основном знать, какое действие выполняет строка кода. В С++ вышеизложенное, однако, может давать совершенно разные интерпретации в зависимости от того, что означает t. Если это тип, то это будет объявление указателя f. Однако, если это не тип, это будет умножение. Таким образом, стандарт С++ говорит в пункте (3/7):

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

Как компилятор узнает, к чему относится имя t::x, если t относится к параметру типа шаблона? x может быть статическим элементом данных int, который может быть умножен или может быть одинаково хорошо вложенным классом или typedef, которые могут привести к объявлению. Если имя имеет это свойство - его нельзя просмотреть до тех пор, пока не будут известны фактические аргументы шаблона, - тогда он называется зависимым именем (он "зависит" от параметров шаблона).

Вы можете порекомендовать просто подождать, пока пользователь не запустит шаблон:

Подождите, пока пользователь не установит шаблон, а затем узнает реальный смысл t::x * f;.

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

Таким образом, должен быть способ сообщить компилятору, что определенные имена являются типами и что некоторые имена не являются.

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

Ответ: Мы решаем, как компилятор должен разбирать это. Если t::x является зависимым именем, тогда нам нужно префикс его typename, чтобы сообщить компилятору разобрать его определенным образом. Стандарт гласит (14.6/2):

Имя, используемое в объявлении или определении шаблона, и которое зависит от параметра шаблона, предполагается, что не следует указывать тип, если соответствующий поиск имени не найдет имя типа или имя не будет квалифицировано по ключевому слову typename.

Существует много имен, для которых 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 только перед квалифицированными именами - поэтому считается, что неквалифицированные имена всегда относятся к типам, если они это делают.

Аналогичная gotcha существует для имен, которые обозначают шаблоны, как это намекает вступительный текст.

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

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

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), operator-function-id (13.5), идентификатора-функции-id (12.3.2) или идентификатора шаблона (14.2), который обозначает объект или метку (6.6.4, 6.1)

Идентификатор - это просто последовательность символов/цифр, а следующие две формы operator + и operator type. Последняя форма template-name <argument list>. Все это имена, и при обычном использовании в стандарте имя также может включать в себя классификаторы, которые говорят, какое пространство имен или класс следует искать в имени.

Значение зависимое выражение 1 + N не является именем, но N is. Подмножество всех зависимых конструкций, которые являются именами, называется зависимым именем. Однако имена функций могут иметь различное значение при разных экземплярах шаблона, но, к сожалению, не пойманы этим общим правилом.

Имена зависимых функций

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

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    
  • 18
    Этот ответ был скопирован из моей предыдущей записи FAQ, которую я удалил, потому что я обнаружил, что мне лучше использовать существующие подобные вопросы, а не создавать новые «псевдо-вопросы» только для того, чтобы ответить на них. Спасибо @Prasoon , который отредактировал идеи последней части (случаи, когда имя / шаблон запрещено) в ответ.
  • 1
    Можете ли вы помочь мне, когда я должен использовать этот синтаксис? this-> template f <int> (); Я получаю эту ошибку «шаблон» (как устранение неоднозначности) допускается только в шаблонах, но без ключевого слова шаблона, он работает нормально.
Показать ещё 15 комментариев
121

С++ 11

Проблема

Хотя правила в С++ 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();
};

Как можно видеть, нам нужно ключевое слово disamiguation, даже если компилятор может понять, что A::result_type может быть только int (и, следовательно, это тип), а this->g может быть только шаблоном-членом g объявлен позже (даже если A явно специализирован где-то, что не повлияет на код внутри этого шаблона, поэтому его значение не может быть затронуто более поздней специализацией A!).

Текущая инстанция

Чтобы улучшить ситуацию, на С++ 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;
}

В С++ 03 язык допускает эту ошибку, потому что никогда не может быть допустимого способа создания экземпляра A<T>::h (любой аргумент, который вы даете T). В С++ 11 язык теперь имеет дополнительную проверку, чтобы дать больше причин для компиляторов реализовать это правило. Поскольку A не имеет зависимых базовых классов, а A не объявляет член questionable_type, имя A<T>::questionable_type не является членом текущего экземпляра или членом неизвестной специализации. В этом случае не должно быть никакого способа, чтобы этот код мог корректно компилироваться во время создания экземпляра, поэтому язык запрещает имя, в котором спецификатор является текущим экземпляром, не является ни членом неизвестной специализации, ни членом текущего экземпляра (однако, это нарушение по-прежнему не требуется для диагностики).

Примеры и мелочи

Вы можете попробовать эти знания в этом ответе и посмотреть, действительно ли эти определения имеют смысл для вас на примере реального мира (они повторяются немного менее подробно в этом ответ).

Правила С++ 11 делают неправильный код действующего С++ 03 (который не был предназначен комитетом С++, но, вероятно, не будет исправлен)

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(); 
}

Этот действительный код С++ 03 привязывает this->f к A::f во время создания экземпляра, и все в порядке. Однако С++ 11 привязывает его к B::f и требует двойной проверки при создании экземпляра, проверяя соответствие совпадения. Однако при создании экземпляра C<A>::g применяется правило

85

ПРЕДИСЛОВИЕ

Этот пост должен быть легко читаемой альтернативой litb post.

Основная цель - то же самое; объяснение "Когда?" и почему?" typename и template.


Какова цель typename и template?

typename и template могут использоваться в обстоятельствах, отличных от объявления шаблона.

В С++ есть определенные контексты, где компилятору должно быть явно сказано, как обрабатывать имя, и все эти контексты имеют одну общую черту; они зависят, по крайней мере, от одного параметра шаблона.

Мы ссылаемся на такие имена, где может быть двусмысленность в интерпретации, как; "зависимые имена".

В этом сообщении будет приведено объяснение взаимосвязи между зависимыми именами и двумя ключевыми словами.


SNIPPET ГОВОРИТ БОЛЕЕ 1000 СЛОВ

Попробуйте объяснить, что происходит в следующем шаблоне функции, либо для себя, друга, либо, возможно, для вашей кошки; что происходит в заявлении, отмеченном (A)?

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


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

Различные T могут радикально изменить вовлеченную семантику.

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.



ОБОСНОВАНИЕ

Стандарт С++ заботится о нашей безопасности и благополучии, по крайней мере в этом случае.

Чтобы предотвратить потенциальную угрозу реализации от неприятных сюрпризов, Стандарт требует, чтобы мы разобрались в двусмысленности зависимого имени, явно указав намерение в любом месте, где мы хотели бы обрабатывать имя как имя типа, или идентификатор шаблона.

Если ничего не указано, зависимое имя будет считаться либо переменной, либо функцией.



КАК ОБРАБОТАТЬ ЗАВИСИМЫЕ ИМЕНА?

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

У нас есть четыре зависимых имени в приведенном выше фрагменте:

  • Е)
    • "type" зависит от экземпляра SomeTrait<T>, который включает T и;
  • F)
    • "NestedTrait" , который является идентификатором шаблона, зависит от SomeTrait<T> и;
    • "тип" в конце (F) зависит от NestedTrait, который зависит от SomeTrait<T> и;
  • G)
    • "данные" , который выглядит как шаблон функции-члена, косвенно является зависимым именем, поскольку тип 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
}

Каждый раз, когда имя обозначает тип, участвующие имена all должны быть либо именами типов, либо пространствами имен, поэтому это довольно легко понять, что мы применяем 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 */ {
       ...
     };
    


  • Если идентификатором шаблона является тот, который указан в производном классе using-директив

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
19
typedef typename Tail::inUnion<U> dummy;

Однако я не уверен, что реализация inUnion верна. Если я правильно понимаю, этот класс не должен быть создан, поэтому вкладка "fail" никогда не будет автоматически терпеть неудачу. Возможно, было бы лучше указать, находится ли тип в объединении или нет с простым логическим значением.

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: посмотрите справочники, особенно в книге Андрея Александреску: Modern С++ Design

  • 0
    inUnion <U> был бы создан, если бы вы, например, попытались вызвать Union <float, bool> :: operator = (U) с U == int. Он вызывает частный набор (U, inUnion <U> * = 0).
  • 0
    И работа с result = true / false заключается в том, что мне нужно boost :: enable_if <>, что несовместимо с нашей текущей цепочкой инструментов OSX. Хотя отдельный шаблон все еще хорошая идея.
Показать ещё 5 комментариев
17

<суб > Этот ответ должен быть довольно коротким и сладким, чтобы ответить (часть) озаглавленного вопроса. Если вы хотите получить более подробный ответ, объясняющий, почему вы должны их там положить, перейдите здесь. Суб >


Общее правило размещения ключевого слова 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, так как это наиболее краткое объяснение, которое я прочитал по этому вопросу.

В шаблоне, который мы пишем, есть два типа имен, которые могут быть использованы - зависимые имена и non- зависимые имена. Зависимое имя - это имя, которое зависит от параметра шаблона; зависимое имя non- имеет то же значение, независимо от параметров шаблона.

Например:

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

На первом этапе анализатор должен знать, является ли зависимое имя именем типа или имени типа non-. По умолчанию зависимым именем считается имя типа non-. Ключевое слово typename перед зависимым именем указывает, что это имя типа.


Резюме

Используйте ключевое слово typename только в объявлениях шаблонов и определениях, если у вас есть квалифицированное имя, которое относится к типу и зависит от параметра шаблона.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню