20

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?

Catskul
  • 17,916
  • 15
  • 84
  • 113
foxcub
  • 2,517
  • 2
  • 27
  • 27
  • 1
    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. It will be confusing later on. – Grzegorz Aug 29 '12 at 22:56
  • I don't know what the standard says, but I am happy that g++ complains... that's just silly. – Karoly Horvath Aug 29 '12 at 22:59
  • 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 Aug 29 '12 at 23:03
  • 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 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 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 Mar 29 '16 at 07:10

2 Answers2

13

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.

Community
  • 1
  • 1
Jesse Good
  • 50,901
  • 14
  • 124
  • 166
  • How about the second form? Is it also an error, and g++ simply doesn't report it? – foxcub Aug 29 '12 at 23:10
  • 3
    @foxcub: In the second form you're not referring to `A`, you're referring to `::A`. The second form is correct. – Lily Ballard Aug 29 '12 at 23:13
  • @foxclub: The second form is fine (as Kevin Ballard explains), because the meaning of `A` does not change. – Jesse Good Aug 29 '12 at 23:30
  • 1
    Interesting, but still mysterious. Why doesn't the meaning of A change? Before the typedef, it means the same thing as ::A, i.e., a template class. After the typedef, it means a very concrete class, a specific instantiation of ::A, namely, ::A. I'm glad to hear that the second form is sound, but help me understand. – foxcub Aug 30 '12 at 00:18
  • @foxclub: The `` part is irrelevant, the name of the template is `A`. Think of it in terms of functions, in the declaration `void foo();`, the name of the function is `foo`, the `()` is not part of the name. – Jesse Good Aug 30 '12 at 00:50
  • @foxclub: After thinking some more, I think what you are missing is the understanding of "what is a name?" The name is simply `A`, and what is of importance here, is what that name refers to inside of the class. – Jesse Good Aug 30 '12 at 00:57
  • 1
    foxclub is right that there is something strange about how GCC handles this. Firstly, `typedef ::A A` _does_ change the meaning of `A`, just like `typedef A A` does. Secondly, if you do `typedef int X;` on the global level, and then `typedef float X;` inside the class definition, this changes the meaning of `X`, but there is no problem whatsoever. Using typedef to redefine the meaning of a name in a separate scope (in this case, the class scope) is perfectly fine. (Of course it is also confusing and I wouldn't recommend doing it.) – jogojapan Aug 30 '12 at 03:46
  • @jogojapan: Perhaps "change the meaning" is the wrong term. In my eyes, `typedef ::A A` does not change what `A` *refers to*. Would you agree with that? As from the standard quote: `shall **refer to** the same declaration` – Jesse Good Aug 30 '12 at 03:59
  • @jogojapan: I think you have to use `X` for it to become an error. [See here](http://ideone.com/3v6Nv). – Jesse Good Aug 30 '12 at 04:14
  • 1
    @JesseGood (about the first comment) I agree the word _meaning_ is vague and perhaps inappropriate in this context. But still, if you do `typedef ::A A;`, and then later (e.g. somewhere in a member function, possibly outside the class definition) do `A a;`, then this `A` will refer to the typename `::A`, no longer to the template name `::A`, so it's changed. Or perhaps I misunderstand what _to refer to_ means in this case. – jogojapan Aug 30 '12 at 04:23
  • 1
    @JesseGood (about the Ideone example) Yes! Actually, this example is what I think the standard quote is actually about: `X` is defined as `float` in the class, and because of how _class scope_ works (which is what §3.3.7/1 is about), that definition must extend to the entire class scope, including separately defined member functions, and including anything found _before_ the actual typedef. So the declaration `X x` becomes ambiguous. (You get the same problem if you have `typedef ::A A;` and put a member `A a;` (or `A a;`) _before_ the typedef.) – jogojapan Aug 30 '12 at 04:28
  • 1
    @jogojapan: Very good point, I will mention your comment in my answer. – Jesse Good Aug 30 '12 at 04:37
  • Thanks for your clarifications. @jogojapan's comments stated exactly what was confusing me. – foxcub Aug 30 '12 at 14:16
  • @TonyD My comments were about the claim that `typedef ::A A;` didn't change what `A` refers to. But it does change it. So that cannot be the reason for the GCC error message. (What you are saying is that `typedef A A` (unlike `typedef ::A A`, and unlike `typedef float X`) is recursive in the sense that the definiens contains the definiendum. So perhaps this recursiveness is the reason for the error message. But that's not what the answer above says, is it?) – jogojapan May 09 '13 at 02:50
2

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.

Catskul
  • 17,916
  • 15
  • 84
  • 113
pyrachi
  • 800
  • 6
  • 15