20
votes

When I compile the following snippet with g++

template<class T>
class A
{};

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

the compiler tells me

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

On the other hand, if I change the typedef to

typedef ::A<T> A;

everything compiles fine with g++. Clang++ 3.1 doesn't care either way.

Why is this happening? And is the second behavior standard?

2
It must be warning level which by default shows it as an error. The same as you can have a function missing return and can be reported as an error or warning. In general, I would avoid declaring type A as a A<T>. It will be confusing later on.Grzegorz
I don't know what the standard says, but I am happy that g++ complains... that's just silly.Karoly Horvath
I think it's neither silly, nor confusing. I run into this problem quite often. As for warning to error conversion, I'm not giving g++ any flags, what warnings does it convert to errors by default?foxcub
This is actually a bit subtler than that. In case a global level A is used before the local A is declared, this error will occur. That said, any use of A (declaring a member) before typedef ::A<T> A; will yield the same error. Moving the declaration below will change the A to a local one and also fix the error. It's the same with the typedef A<T> A, you're using the global A before, "on the left" and then redeclaring it immediately "on the right". This is just g++ making sure that all the occurrences of A in the class will have the same meaning (not ::A, that doesn't change).the swine

2 Answers

13
votes

g++ is correct and conforming to the standard. From [3.3.7/1]:

A name N used in a class S shall refer to the same declaration in its context and when re-evaluated in the completed scope of S. No diagnostic is required for a violation of this rule.

Before the typedef, A referred to the ::A, however by using the typedef, you now make A refer to the typedef which is prohibited. However, since no diagnostic is required, clang is also standard conforming.

jogojapan's comment explains the reason for this rule. Take the following change to your code:

template<class T>
class A
{};

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

Because of how class scope works, A a; becomes ambiguous.

2
votes

I will add to Jesse's answer about the seemingly peculiar behavior of GCC in compiling:

typedef A<T> A;

vs

typedef ::A<T> A;

This also applies to using statements as well of the form:

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

What seems to be happening within GCC, is that during the compilation of the typedef/using statement declaring B::A, that the symbol B::A becomes a valid candidate within the using statement itself. I.e. when saying using A = A<T>; or typedef A<T> A; GCC considers both ::A and B::A valid candidates for A<T>.

This seems odd behavior because as your question implies, you don't expect the new alias A to become a valid candidate within the typedef itself, but as Jesse's answer also says, anything declared within a class becomes visible to everything else inside the class - and in this case apparently even the declaration itself. This type of behavior may be implemented this way to permit recursive type definitions.

The solution as you found is to specify for GCC precisely which A you're referring to within the typedef and then it no longer complains.