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 все равно.

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

Ответы

Ответ 1

g++ является правильным и соответствует стандарту. Из [3.3.7/1]:

Имя N, используемое в классе 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; становится неоднозначным.

Ответ 2

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

typedef A<T> A;

против

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 именно то, к которому вы обращаетесь в пределах typedef, а затем оно больше не жалуется.