typedef changes meaning
Asked Answered
P

2

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?

Pelagias answered 29/8, 2012 at 22:47 Comment(4)
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.Fiscus
I don't know what the standard says, but I am happy that g++ complains... that's just silly.Hounding
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?Pelagias
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).Fatherhood
S
14

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.

Stendhal answered 29/8, 2012 at 23:7 Comment(14)
How about the second form? Is it also an error, and g++ simply doesn't report it?Pelagias
@foxcub: In the second form you're not referring to A, you're referring to ::A. The second form is correct.Watermelon
@foxclub: The second form is fine (as Kevin Ballard explains), because the meaning of A does not change.Stendhal
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<T>. I'm glad to hear that the second form is sound, but help me understand.Pelagias
@foxclub: The <T> 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.Stendhal
@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.Stendhal
foxclub is right that there is something strange about how GCC handles this. Firstly, typedef ::A<T> A does change the meaning of A, just like typedef A<T> 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.)Plunge
@jogojapan: Perhaps "change the meaning" is the wrong term. In my eyes, typedef ::A<T> A does not change what A refers to. Would you agree with that? As from the standard quote: shall **refer to** the same declarationStendhal
@jogojapan: I think you have to use X for it to become an error. See here.Stendhal
@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<T> 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<T>, no longer to the template name ::A, so it's changed. Or perhaps I misunderstand what to refer to means in this case.Plunge
@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<T> A; and put a member A a; (or A<T> a;) before the typedef.)Plunge
@jogojapan: Very good point, I will mention your comment in my answer.Stendhal
Thanks for your clarifications. @jogojapan's comments stated exactly what was confusing me.Pelagias
@TonyD My comments were about the claim that typedef ::A<T> 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<T> A (unlike typedef ::A<T> 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?)Plunge
B
3

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.

Berget answered 30/1, 2014 at 18:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.