несколько вложенных зависимых имен - куда вставлять ключевое слово typename? - PullRequest
9 голосов
/ 10 июля 2011

Этот вопрос был вдохновлен другим вопросом .Пытаясь ответить на этот вопрос, я поняла, что у меня много вопросов.Итак ... Рассмотрим следующее:

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 ]

7 голосов
/ 11 июля 2011

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


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

Полное имя, используемое в качестве имени в mem-initializer-id, base-спецификаторе или подробном спецификаторе типа, неявно предполагается для имени типа без использования ключевого слова 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 не обозначает тип, программа некорректна.

3 голосов
/ 11 июля 2011

Смысл 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%.

2 голосов
/ 10 июля 2011
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 и по умолчанию оно будет проанализировано как не тип

0 голосов
/ 10 июля 2011

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.

0 голосов
/ 10 июля 2011

typedef typename T :: Type :: Type Type; // (1) // правовая

Я сам не понимаю необходимость typename здесь. Becuase typedef может применяться только к typename. Возможно, грамматика C ++ разработана таким образом.

enum {value = T :: Type :: Type :: value}; // (2) // правовая

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

...