typedef меняет значение

Когда я компилирую следующий фрагмент с g++

template<class T>
class A
{};

template<class T>
class B
{
    public:
        typedef A<T> A;
};

компилятор говорит мне

error: declaration of ‘typedef class A<T> B<T>::A’
error: changes meaning of ‘A’ from ‘class A<T>’

С другой стороны, если я изменю typedef на

typedef ::A<T> A;

все отлично компилируется с g++. Clang++ 3.1 все равно.

Почему это происходит? А второй стандарт поведения?


person foxcub    schedule 29.08.2012    source источник
comment
Это должен быть уровень предупреждения, который по умолчанию показывает это как ошибку. Точно так же, как у вас может быть отсутствующий возврат функции, о котором может быть сообщено как об ошибке или предупреждении. В общем, я бы не стал объявлять тип A как A‹T›. Это будет путать позже.   -  person Grzegorz    schedule 30.08.2012
comment
Я не знаю, что говорит стандарт, но я рад, что g++ жалуется... это просто глупо.   -  person Karoly Horvath    schedule 30.08.2012
comment
Я думаю, что это не глупо и не запутанно. Я довольно часто сталкиваюсь с этой проблемой. Что касается предупреждения о преобразовании ошибок, я не даю g++ никаких флагов, какие предупреждения он по умолчанию преобразует в ошибки?   -  person foxcub    schedule 30.08.2012
comment
Это на самом деле немного тоньше, чем это. В случае, если глобальный уровень A используется до объявления локального A, возникает эта ошибка. При этом любое использование A (объявление члена) до typedef ::A<T> A; выдаст ту же ошибку. Перемещение объявления ниже изменит A на локальное, а также исправит ошибку. То же самое и с typedef A<T> A, вы используете глобальное A раньше, слева, а затем сразу же повторно объявляете его справа. Это просто g++, гарантирующий, что все вхождения A в классе будут иметь одно и то же значение (не ::A, это не меняется).   -  person the swine    schedule 29.03.2016


Ответы (2)


g++ корректен и соответствует стандарту. Из [3.3.7/1]:

Имя N, используемое в классе S, должно ссылаться на одно и то же объявление в его контексте и при повторной оценке в завершенной области действия S. Для нарушения этого правила диагностика не требуется.

До typedef A ссылался на ::A, однако, используя typedef, теперь вы заставляете A ссылаться на typedef, что запрещено. Однако, начиная с no diagnostic is required, clang также соответствует стандарту.

комментарий jogojapan объясняет причину этого правила. Внесите следующие изменения в свой код:

template<class T>
class A
{};

template<class T>
class B
{
    public:
        A a; // <-- What "A" is this referring to?
        typedef     A<T>            A;
};

Из-за того, как работает область класса, A a; становится неоднозначным.

person Jesse Good    schedule 29.08.2012
comment
Как насчет второй формы? Это тоже ошибка, и g++ просто не сообщает об этом? - person foxcub; 30.08.2012
comment
@foxcub: Во второй форме вы имеете в виду не A, а ::A. Вторая форма правильная. - person Lily Ballard; 30.08.2012
comment
@foxclub: со второй формой все в порядке (как объясняет Кевин Баллард), потому что значение A не меняется. - person Jesse Good; 30.08.2012
comment
Интересно, но все же загадочно. Почему значение А не меняется? Перед typedef это означает то же самое, что и ::A, то есть класс-шаблон. После typedef это означает очень конкретный класс, конкретное воплощение ::A, а именно ::A‹T›. Я рад слышать, что вторая форма правильная, но помогите мне понять. - person foxcub; 30.08.2012
comment
@foxclub: Часть <T> не имеет значения, имя шаблона A. Думайте об этом с точки зрения функций, в объявлении void foo(); имя функции foo, () не является частью имени. - person Jesse Good; 30.08.2012
comment
@foxclub: Подумав еще немного, я думаю, что вам не хватает понимания того, что такое имя? Имя просто A, и здесь важно то, к чему относится это имя внутри класса. - person Jesse Good; 30.08.2012
comment
foxclub прав, что есть что-то странное в том, как GCC справляется с этим. Во-первых, typedef ::A<T> A действительно меняет значение A, как и typedef A<T> A. Во-вторых, если вы сделаете typedef int X; на глобальном уровне, а затем typedef float X; внутри определения класса, это изменит значение X, но проблем не возникнет. Использование typedef для переопределения значения имени в отдельной области (в данном случае в области класса) совершенно нормально. (Конечно, это также сбивает с толку, и я бы не рекомендовал этого делать.) - person jogojapan; 30.08.2012
comment
@jogojapan: Возможно, изменить значение - неправильный термин. На мой взгляд, typedef ::A<T> A не меняет того, что A относится. Ты бы согласился с этим? Как из стандартной цитаты: shall **refer to** the same declaration - person Jesse Good; 30.08.2012
comment
@jogojapan: я думаю, вам нужно использовать X, чтобы это стало ошибкой. см. здесь. - person Jesse Good; 30.08.2012
comment
@JesseGood (о первом комментарии) Я согласен, что слово значение расплывчато и, возможно, неуместно в данном контексте. Но тем не менее, если вы делаете typedef ::A<T> A;, а затем позже (например, где-то в функции-члене, возможно, вне определения класса) делаете A a;, то это A будет ссылаться на имя типа ::A<T>, а не на имя шаблона ::A, поэтому оно изменено. Или, возможно, я неправильно понимаю, что означает сослаться в данном случае. - person jogojapan; 30.08.2012
comment
@JesseGood (о примере Ideone) Да! На самом деле, этот пример — то, о чем я думаю, что стандартная цитата на самом деле: X определяется как float в классе, и из-за того, как работает область действия класса (о чем и говорится в §3.3.7/1). ), это определение должно распространяться на всю область действия класса, включая отдельно определенные функции-члены и включая все, что найдено до фактического определения типа. Таким образом, объявление X x становится неоднозначным. (Вы получите ту же проблему, если у вас есть typedef ::A<T> A; и вы поместите элемент A a; (или A<T> a;) перед typedef.) - person jogojapan; 30.08.2012
comment
@jogojapan: Очень хороший момент, я упомяну ваш комментарий в своем ответе. - person Jesse Good; 30.08.2012
comment
Спасибо за ваши разъяснения. В комментариях @jogojapan указано, что именно меня смущало. - person foxcub; 30.08.2012
comment
@TonyD Мои комментарии касались утверждения, что typedef ::A<T> A; не меняет того, на что ссылается A. Но это меняет его. Так что это не может быть причиной сообщения об ошибке GCC. (Вы говорите, что typedef A<T> A (в отличие от typedef ::A<T> A и в отличие от typedef float X) является рекурсивным в том смысле, что определение содержит определение. Поэтому, возможно, эта рекурсивность является причиной сообщения об ошибке. Но это не то, что говорится в ответе выше, это Это?) - person jogojapan; 09.05.2013

Я добавлю к ответу Джесси о, казалось бы, своеобразном поведении GCC при компиляции:

typedef A<T> A;

vs

typedef ::A<T> A;

Это также относится к использованию операторов в форме:

using A =   A<T>;
using A = ::A<T>;

Кажется, что в GCC происходит то, что во время компиляции оператора typedef/using, объявляющего B::A, символ B::A становится допустимым кандидатом внутри самого оператора using. т.е. при произнесении using A = A<T>; или typedef A<T> A; GCC считает как ::A, так и B::A допустимыми кандидатами на A<T>.

Это кажется странным поведением, потому что, как следует из вашего вопроса, вы не ожидаете, что новый псевдоним A станет допустимым кандидатом в самом typedef, но, как также говорит ответ Джесси, все, что объявлено в классе, становится видимым для всего остального внутри класса - и в этом случае, по-видимому, даже сама декларация. Этот тип поведения может быть реализован таким образом, чтобы разрешить определение рекурсивного типа.

Решение, которое вы нашли, состоит в том, чтобы указать для GCC, какой именно A вы имеете в виду в typedef, и тогда он больше не жалуется.

person pyrachi    schedule 30.01.2014