Несколько вложенных зависимых имен - куда вставлять ключевое слово 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 и Online 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 }; 

Конечно, обходной путь должен использовать последовательный typedef вот так:

   typedef typename T::Type T1;
   typedef typename T1::Type Type;
   enum { value = Type::value};  

Хороший стиль остался в стороне, нужно ли нам синтаксически использовать обходной путь, о котором я говорил?

Остальное просто интересный пример. Не нужно читать. Не то, что имеет отношение к вопросу.

Обратите внимание, что хотя MSVC принимает оригинальный странный синтаксис без нескольких typename s (я имею в виду (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 ответов

Решение

Имя перед оператором области :: всегда должно быть именем пространства имен или класса (или перечисления), а имена пространства имен не могут быть зависимыми. Так что вам не нужно сообщать компилятору, что это имя класса.


Я не просто придумываю это, стандарт говорит (раздел [temp.res]):

Полное имя, используемое в качестве имени в mem-initializer-id, базовом спецификаторе или в сложном спецификаторе типа, неявно предполагается для имени типа, без использования typename ключевое слово. В спецификаторе вложенного имени, который непосредственно содержит спецификатор вложенного имени, который зависит от параметра шаблона, предполагается, что идентификатор или простой-шаблон-идентификатор неявно предполагают имя типа без использования 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; выражение выражения или декларация pointee 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 Вот. Becuase typedef может применяться только к typename, Возможно, грамматика C++ разработана таким образом.

enum {value = T:: Type:: Type:: value}; // (2) // законно?

Вы не можете использовать typename потому что это должно быть значение. Это неявно логично, что когда вы пишете enum { value = ??? };, затем ??? всегда должно быть только значением.

typename относится к первому зависимому типу. В вашем конкретном случае:

typedef typename T::type1::type2 Type;

это относится к T::type1говоря, что это зависимое имя (в зависимости от параметра шаблона T).

Для постоянного значения вам не нужно имя типа, потому что это значение, а не тип. Если значение не определено, вы получите ошибку компиляции.

РЕДАКТИРОВАТЬ

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> > > > Тип это: так как Т S3<S2<S2<S1> > >, затем typename T::Type расширяется до S2<S2<S1> >::Type, который является полным типом (никак не зависит от параметра шаблона). По этой причине вам не нужно использовать typename после первого зависимого typename.

Другие вопросы по тегам