воскресенье, августа 27, 2006

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

typename - это одно из редких и экзотичных ключевых слов, которые с большой долей вероятности никогда не пригодятся в работе (мне оно еще не пригождалось), но все равно любопытно зачем оно нужно. Его иногда путают с typedef. Несмотря на то, что они слегка похожи по названию, они означают разные вещи.

typename нужен для облегчения задачи компилятору при парсинге загадочных выражений вроде следующего:
template<class T> void f() { T::x * p; ... }

Выражение T::x * p может означать две вещи.
Первая: x - это имя некого типа, а все выражение есть объявление указателя p.
Вторая: x - это статическая переменная, а * - это знак умножения. Тогда p - это тоже какая-то переменная.

Ключевое слово typename разрешит это недоразумение, явно сказав компилятору, что речь идет о типе.
template<class T> void f() { typename T::x * p; ... }

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

Также typename можно использовать вместо class при описании шаблона. То есть вместо template<class T> можно написать template<typename T>, разницы никакой нет. Но исторически так сложилось, что class употребляется чаще.

Ссылки по теме:
Dr. Dobb's - What Are You, Anyway? - статья о сложностях, которые возникают у компилятора при парсинге шаблонов. В том числе там упоминается typename.
What is the template typename keyword used for? What's the difference between typename and typedef?
comp.lang.c++.moderated Confused about typename
comp.lang.c++.moderated No typename needed before std::list<T>?

17 коммент.:

Maniac комментирует...

Насколько я знаю, все шаблоны раскрываются не в рантайме, а на этапе компиляции, как define'ы. Почему же тогда компилятор не может сначала одним проходом раскрыть все шаблоны и уже тогда, точно зная, что там на месте T, понять, что такое T::x?

dkmike комментирует...

Даже в таких случаях

template <class StorableElement>
typename LinkedList<StorableElement>::LinkedListElement* LinkedList<StorableElement>::LinkedListElement::GetNext() const {
...
}

где умножение вряд ли возможно, компилятор MSVS 2003 разобраться без typename не может.

Not a kernel guy комментирует...

Есть еще один замечательный пример:

template<class T>
class Y {
T::A a;
T::A(b);
};

В обоих случаях компилятор не знает, что такое T::A. Это может быть именем типа, а может быть и именем функции или переменной. Причем компилятор не может гарантировать, что к моменту раскрытия Y, тип T уже будет известен (просто потому, что возможны циклические зависимости между этими типами). В общем без подсказки в виде typename (или мощной эвристики) никак.

И, кстати, стоит упомянуть, что typename рекомендуется использовать вместо class при объявлении шаблона. Например:

template <typename T>
...

Впрочем это ни на что кроме стиля не влияет.

rs комментирует...

typename необходимо исключительно в шаблонах, так что если шаблоны Вы не используете, то оно Вам не нужно. Хотя по моему шаблоны - одна из самых полезных вещей С++.

Единственная поправка к посту - по стандарту _везде_ где используется имя типа, зависящее от параметра шаблона и содержащее "::" _обязательно_ должно применяться typename, то что некоторые компиляторы способны обойтись без него - явление временное.

Maniac:
1) Не стоит даже сравнивать шаблоны и макросы. Последние не имеют области видимости, применяются на этапе препроцессинага, а не компиляции. Макросы в С++ вообще - зло :) (за исключением предотвращения повторного включения и выбора платформозависимого кода для компиляции). Опять же, кто нибудь слышал об отладке макросов? :)

2) Формально, вывести не всегда можно. Например, в С++ есть extern шаблоны (правда пока больше на бумаге, чем в ральности, и по экспериментам по поддержке этой возможности - не все уверены что она нужна, но это отдельный разговор), которые _предкомпилируются_, без знания конкретного типа T. В посте же приведен пример, почему по просто записи T::a нельзя сказать - что это, static переменная или имя типа.

Опять же в gcc явно выполняется синтаксический разбор шаблона - даже если он не инстанцируется, то в нем находятся синтаксические ошибки. Это еще не компиляция extern шаблонов, но уже проверка синтаксической коректности, для которой надо без самого типа T уже использовать зависимые имена - T::x.

MSVC - прямая противоположность - если метод нигде не инстанцирован то ошибка (даже синтаксическая - что меня всегда добивает) в нем компилятором может не заметиться. И кстати именно MSVC - именно тот компилятор, кторый может _иногда_ скомпилировать без typename. Что скорее всего явление временное, т.к. в MS над С++ среди прочих работет Sutter, и одна из основных целей, как они сами заявляют - максимальная поддержка стандарта (и библиотек boost).

По моему лучше поведение gcc. Хотя оно и требует обязательного использования typename (что соотвествует стандарту), но зато ты уверен что код собирается (да, я ленив и на некоторые простые функции не хочу писать unit test-ы :) )

lrrr комментирует...

Вот тут вот интересное обсуждение typename и хороший пример неоднозначности при парсинге a<T::b>c.

Alena комментирует...

Насколько я знаю, все шаблоны раскрываются не в рантайме, а на этапе компиляции, как define'ы. Почему же тогда компилятор не может сначала одним проходом раскрыть все шаблоны и уже тогда, точно зная, что там на месте T, понять, что такое T::x?

Ну тебе уже вкратце сказали, что оно там не как define'ы. Компилятору нужно распарсить шаблон до того, как начать его использовать. А если компилятор не знает что там - тип или переменная, то распарсить не получится.

Но даже если бы в компиляторах попытались строить догадки, то там все равно возникают проблемы. Что если ты попытался использовать шаблон дважды, в одном месте использовав класс с типом, а в другом - класс с переменной? Кроме того, я видела попытки скормить компилятору структуру struct A{ struct x{}; static int x;};

CyberZX комментирует...

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

Единственная причина беспрекословного требования typename современными компиляторами - это следование букве Стандарта. Что бы повышать частоту и переносимость кода. В VC2003-ем без typename можно было жить свободно. В VC2005-ом это уже не пройдет.

Я надеюсь, что в стандарте С++0x все же избавятся от обязательного требования typename при обращении к внутренним типам параметров шаблона.

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

Вот простенький пример.
static const int types_count = boost::mpl::size<types_list>::value;
 template<int n = 0>
 struct generate_info_map
 {
  BOOST_STATIC_ASSERT(n < types_count);
  typedef typename generate_info_map<n+1>::type upper_map;
  typedef typename at<types_list, long_<n> >::type type_value;
  typedef    long_<1 << n>     type_key;
  typedef typename insert<upper_map, pair<type_value, type_key> >::type type;        
 };

Miguel комментирует...

Хотя по моему шаблоны - одна из самых полезных вещей С++.
Ну, по-моему, шаблоны там - вообще единственная полезная вещь...

Prokrust комментирует...

Целиком согласен с CyberZX.
Впрочем если до сих пор у всех известных мне компиляторов очень часто выдача строки ошибки компиляции вызвывает у меня нездоровый смех, то надеяться не на что.
PS. Этот язык сгубит высокомерие разработчиков компилятора.

icestudent комментирует...

Prokrust:
> очень часто выдача строки ошибки компиляции вызвывает у меня нездоровый смех
Ахха.. И для этого придумали парсер: STL Error Message Decryptor (www.bdsoft.com/tools/stlfilt.html)

Prokrust комментирует...

> icestudent: "парсер: STL Error Message Decryptor"
Парсер для С++ написан на Perl - я в шоке!
PS. Вот так вскоре и компилятор С++ на PHP напишут.

Григорий Зубанков комментирует...

Добавлю, что при использовании вложенных шаблонов одного typename для корректного разбора конструкции компилятором недостаточно:

template < class T >
struct foo
{
template < class T1 > struct bar {};
};


template < class T, class T1 >
void test(typename foo< T >::bar< T1 >) // ошибка
{
}

Компилятору надо обязательно сообщить о том, что вложенный тип тоже является шаблоном:

template < class T, class T1 >
void test(typename foo< T >::template bar< T1 >)
{
}

argenet комментирует...

Столкнулся именно с этой проблемой, когда компилировал древнюю ZipIOS++ библиотеку (zipios.sourceforge.net) на GCC 4.
Со старым GCC 2.95.3 всё проходило на ура, а здесь как раз нехватка typename и сказалась %)

Анонимный комментирует...

Спасибо Вам, Алёна. Я периодически об этом забываю - вспоминаю, когда переношу код для GCC :)

Anika комментирует...

template< class T>
void func( std::list< T>::iterator it)
{
}
int main()
{
std::list< int> l;
func< int>( l.begin());
return 0;
}

Ms Visual C++ 2010 уже ругается:
warning C4346: std::list::iterator: зависимое имя не является типом укажите префикс "typename" для задания типа
error C2146: синтаксическая ошибка: отсутствие ")" перед идентификатором "it"
error C2182: func: недопустимое использование типа "void"

И так далее =)))

Руслан комментирует...

Вроде не были упомянуты шаблонные шаблонные параметры а именно:
// вот так надо
template class Z> class SuperClass
{

};

// так нельзя заменить
template typename Z> class SuperClass
{

};

Руслан комментирует...

блин, блогспот темплэйты слева сожрал. Ну думаю все поняли, что я имел в виду))