несколько вложенных зависимых имен - куда вставить ключевое слово typename?
Этот вопрос был вдохновлен этим другим вопросом. Пытаясь ответить на этот вопрос, я понял, что у меня самого есть много вопросов. Так... Рассмотрим следующее:
struct S1
{
enum { value = 42 };
};
template <class T> struct S2
{
typedef S1 Type;
};
template <class T> struct S3
{
typedef S2<T> Type;
};
template <class T> struct S4
{
typedef typename T::Type::Type Type; //(1)//legal?
enum {value = T::Type::Type::value }; //(2)//legal?
};
int main()
{
S4<S3<S2<S2<S1> > > >::value;
}
Это успешно компилируется с MSVC9. 0 и онлайн Comeau. Однако, меня беспокоит то, что я не понимаю, что typename относится к (1) и почему нам не нужно typename в (2).
Я попробовал эти 2 синтаксиса (syntaces?) из того, что я думаю, что он должен будьте оба из которых терпят неудачу на MSVC:
typedef typename T::typename Type::Type Type;
enum {value = typename T::typename Type::Type::value };
И
typedef typename (typename T::Type)::Type Type;
enum {value = (typename (typename T::Type)::Type)::value };
Конечно, обходной путь состоит в использовании последовательных typedefs, таких как:
typedef typename T::Type T1;
typedef typename T1::Type Type;
enum { value = Type::value};
Хороший стиль, оставленный в стороне, делаем ли мы синтаксически придется использовать обходной путь, о котором я упоминал?
остальное-просто интересный пример. Нет необходимости читать. Это не имеет отношения к вопросу.
Обратите внимание, что хотя MSVC принимает оригинальный странный синтаксис без множественных typenames (I значит (1) и (2)), это приводит к странному поведению, как в упомянутом вопросе. Я думаю, что приведу этот пример в сжатой форме и здесь:
struct Good
{
enum {value = 1};
};
struct Bad
{
enum {value = -1};
};
template <class T1, class T2>
struct ArraySize
{
typedef Bad Type;
};
template <class T>
struct ArraySize<T, T>
{
typedef Good Type;
};
template <class T>
struct Boom
{
char arr[ArraySize<T, Good>::Type::value]; //error, negative subscript, even without any instantiation
};
int main()
{
Boom<Good> b; //with or without this line, compilation fails.
}
Это не компилируется. Обходной путь, который я упомянул, решает проблему, но я уверен, что проблема здесь - мой первоначальный вопрос-отсутствует имя типа, но вы действительно не знаете, куда его вставить.
Заранее большое спасибо.
5 ответов:
Имя перед оператором scope
::всегда должно быть именем пространства имен или класса (или перечисления), и имена пространств имен не могут быть зависимыми. Поэтому вам не нужно говорить компилятору, что это имя класса.
Я не просто выдумываю это, стандарт говорит (раздел
[temp.res]):Полное имя, используемое в качестве имени в идентификаторе инициализатора mem, базовом спецификаторе или разработанном спецификаторе типа, неявно предполагается для имени типа без использования
typenameключевое слово. Вспецификаторе вложенного имени , который непосредственно содержитспецификатор вложенного имени , зависящий от параметра шаблона, идентификатор или simple-template-id неявно предполагается, что имя типа без использования ключевого словаtypename. [ Записка: Ключевое словоtypenameне допускается синтаксисом этих конструкций. - Конечная нота]
T::,T::Type::, иT::Type::Type::являютсяспецификаторами вложенных имен , они не должны быть помеченыtypename.Это раздел явно мог и, возможно, должен был включатьспецификатор типа объявления typedef в список исключений. Но помните, чтоспецификаторы типов могут быть очень сложными, особенно в объявлениях typedef. Прямо сейчас вполне возможно, что ключевое слово
typenameпонадобится несколько раз в спецификаторе типа typedef , поэтому потребуется гораздо больше анализа, чтобы убедить меня, чтоtypenameникогда не нужен в typedef.В
typedef typename T::Type::Type Type,T::Type::Typeтребует использования ключевого словаtypename, поскольку его спецификатор вложенного имени (T::Type::) является зависимым именем, и стандарт говорит (тот же раздел):Если квалифицированный идентификатор предназначен для ссылки на тип, который не является членом текущего экземпляра (14.6.2.1), а его спецификатор вложенного имени ссылается на зависимый тип, он должен быть префиксирован ключевым словом typename, образуя спецификатор typename. Если квалифицированный-идентификатор в параметр typename-описатель не обозначает тип, программа плохо сформирована.
Смысл typename заключается в том, чтобы разрешить базовую проверку определения шаблона перед его созданием. Синтаксический анализ C++ невозможен без знания того, является ли имя типом или нет (является ли
a*b;оператором выражения или объявлением объектаb).В определении шаблона категория (тип или нетип) простого идентификатора всегда известна. Но квалифицированное (зависимое) имя не может быть - для произвольного T, T::x может быть либо тем, либо другим.
Таким образом, язык позволяет вам сказать компилятор, определяющий полное имя типа с помощью ключевого слова typename. В противном случае компилятор должен принять его за тип значения. В любом случае это ошибка, чтобы ввести компилятор в заблуждение.
Правило применяется даже в некоторых случаях, когда очевидно, что тип необходим (например, в typedef).Только полное имя требует этой неоднозначности -
typename A::B::Cговорит вам, что C-тип; нет необходимости знать что-либо о A или B, чтобы проанализировать контекст, в котором появится полное имя.В вашем примере (1) имя типа говорит, что
T::Type::Typeявляется типом. В (2) вы не должны использовать typename, потому чтоT::Type::valueне является типом. Ни в одном случае ничего не говорится оT::Type, потому что это не имеет отношения к делу. (Хотя можно сделать вывод, что это должен быть тип, потому что иначе вы не смогли бы применить к нему::.)Я подозреваю, что ваша проблема с MSVC - это просто ошибка в этом компиляторе (печально известно, что он не обрабатывает двухфазный поиск должным образом), хотя я должен признать Я не уверен на 100%.
typedef typename T::Type::Type Type; //(1)//legal? enum {value = T::Type::Type::value }; //(2)//legal?В (1) Вы говорите, что T:: Type:: Type - это имя типа
В (2) вы ничего не говорите о T:: Type:: Type:: value и по умолчанию он будет проанализирован как не-Тип
Typedef typename T:: Type:: Type Type; /(1)//законно?
Я сам не понимаю необходимости
typenameздесь. Потому чтоtypedefможно применить только кtypename. Может быть, c++ grammer устроен именно так.Перечисление {значение = т::Тип::тип::значение }; /(2)//законно?
Вы не можете использовать
typename, потому что это должно быть значение. Неявно логично, что когда вы пишетеenum { value = ??? };, то???всегда должно быть только значение.
typenameотносится к первому зависимому типу. В вашем конкретном случае:typedef typename T::type1::type2 Type;Он ссылается на
T::type1, сообщая, что это зависимое имя (в зависимости от параметра шаблона T).Для постоянного значения вам не нужно имя типа, потому что это значение, а не тип. Если значение не определено, вы получите ошибку компиляции.
EDIT
struct S1 { enum { value = 42 }; }; template <class T> struct S2 { typedef S1 Type; }; template <class T> struct S3 { typedef S2<T> Type; }; template <class T> struct S4 { typedef typename T::Type::Type Type; //(1)//legal? enum {value = T::Type::Type::value }; //(2)//legal? };Давайте медленно пройдемся по этому примеру. В этом типе
S4<S3<S2<S2<S1> > > >происходит следующее : Так как T естьS3<S2<S2<S1> > >, тоtypename T::Typeрасширяется доS2<S2<S1> >::Type, который является полным типом (больше никак не зависит от параметра шаблона). По этой причине вам не нужно использовать typename после первого зависимого typename.
Comments