рассмотрим приведенный ниже код:
template<class K>
class C {
struct P {};
vector<P> vec;
void f();
};
template<class K> void C<K>::f() {
typename vector<P>::iterator p = vec.begin();
}
Почему в данном примере необходимо ключевое слово "typename"? Существуют ли другие случаи, когда необходимо указывать "typename"?
Краткий ответ: При обращении к вложенному имени, которое является зависимым именем, т.е. вложенным внутрь экземпляра шаблона с неизвестным параметром.
Длинный ответ: В языке Си++ существует три уровня сущностей: значения, типы и шаблоны. Все они могут иметь имена, и само по себе имя не говорит о том, к какому уровню сущностей оно относится. Скорее, информация о природе сущности имени должна быть выведена из контекста.
В тех случаях, когда такой вывод невозможен, его приходится уточнять:
template <typename> struct Magic; // defined somewhere else
template <typename T> struct A
{
static const int value = Magic<T>::gnarl; // assumed "value"
typedef typename Magic<T>::brugh my_type; // decreed "type"
// ^^^^^^^^
void foo() {
Magic<T>::template kwpq<T>(1, 'a', .5); // decreed "template"
// ^^^^^^^^
}
};
Здесь имена Magic<T>::gnarl
, Magic<T>::brugh
и Magic<T>::kwpq
пришлось эксплицировать, поскольку сказать об этом невозможно: Поскольку Magic
является шаблоном, то сама природа типа Magic<T>
зависит от T
- могут существовать специализации, совершенно отличные от первичного шаблона, например.
Что делает Magic<T>::gnarl
зависимым именем, так это тот факт, что мы находимся внутри определения шаблона, где T
неизвестно. Если бы мы использовали Magic<int>
, все было бы иначе, поскольку компилятор знает (обещаем!) полное определение Magic<int>
.
(Если вы хотите проверить это самостоятельно, то вот пример определения Magic
, который вы можете использовать. Извините за использование constexpr
в специализации для краткости; если у вас старый компилятор, смело меняйте объявление статической константы-члена на старую форму, характерную для версии до С++11).
template <typename T> struct Magic
{
static const T gnarl;
typedef T & brugh;
template <typename S> static void kwpq(int, char, double) { T x; }
};
template <> struct Magic<signed char>
{
// note that `gnarl` is absent
static constexpr long double brugh = 0.25; // `brugh` is now a value
template <typename S> static int kwpq(int a, int b) { return a + b; }
};
Использование:
int main()
{
A<int> a;
a.foo();
return Magic<signed char>::kwpq<float>(2, 3); // no disambiguation here!
}
Ключевое слово typename
необходимо, поскольку iterator
является зависимым типом от P
. Компилятор не может понять, относится ли iterator
к значению или к типу, поэтому он считает, что это значение, пока вы не скажете typename
. Он необходим в тех случаях, когда тип зависит от аргумента шаблона, в контексте, когда допустимы либо типы, либо значения. Например, в качестве базовых классов typename
не нужен, поскольку базовый класс должен быть типом.
По этому же поводу существует ключевое слово template
, которое используется для того, чтобы сообщить компилятору, что некоторое зависимое имя является шаблонной функцией, а не значением.
Ключевое слово typename необходимо в тех случаях, когда имя типа зависит от параметра шаблона (чтобы компилятор мог 'знать' семантику идентификатора (тип или значение), не имея при первом проходе полной таблицы символов).
Не в том же значении и несколько реже ключевое слово lone typename может быть полезно при использовании общих параметров шаблона: http://ideone.com/amImX.
#include <string>
#include <list>
#include <vector>
template <template <typename, typename> class Container,
template <typename> class Alloc = std::allocator>
struct ContainerTests
{
typedef Container<int, Alloc<int> > IntContainer;
typedef Container<std::string, Alloc<int> > StringContainer;
//
void DoTests()
{
IntContainer ints;
StringContainer strings;
// ... etc
}
};
int main()
{
ContainerTests<std::vector> t1;
ContainerTests<std::list> t2;
t1.DoTests();
t2.DoTests();
}