What does C++ syntax “A::B:A {};” mean
Asked Answered
F

2

66

What does C++ syntax struct A::B:A {}; mean? Where is this name definition (or access) described in the C++ standard?

#include <iostream>

struct B;

struct A {
    struct B;
};

struct A::B:A {
};

int main() {
    A::B::A::B b;
    std::cout<<"Sizeof A::B::A::B is " << sizeof(A::B::A::B)<<std::endl;
    return 0;
}
Faerie answered 29/11, 2017 at 6:45 Comment(4)
The initial struct B; is unnecessary. It is not used anywhere in the code.Gpo
@AnT Just that it is not used in this translation unit.. But I don't think there are other translation units as far as this example is concerned and there is a good chance the op might have considered it a must.Zamboanga
@sjsam: Even if B was used in another translation unit, the declaration would still be redundant in this TU.Alisa
inner B in namespace A as child from ARepro
B
121

This definition

struct A {
    struct B;
};

Defines a struct A with a declaration of a nested struct B1. The fully qualified name of B is A::B, you could say B is inside the "namespace" of A. Then this:

struct A::B : A { // Note I added spaces
};

Is the definition of A::B, and the single : specifies that it is derived from A.

Now, the interesting part is A::B::A::B. Let's dissect it:

  1. A::B names the nested structure.
  2. A::B::A accesses the injected class name A inside B. The injection is due to the inheritance.
  3. A::B::A::B names the nested structure B in A again.

And you can continue ad-infinitum, or at least until your compiler meets its translation limit2.

A fun intellectual exercise, but avoid like the plague in actual code.


[class.qual]/1 explains how the lookup works

If the nested-name-specifier of a qualified-id nominates a class, the name specified after the nested-name-specifier is looked up in the scope of the class ([class.member.lookup]), except for the cases listed below. The name shall represent one or more members of that class or of one of its base classes (Clause [class.derived]).

And the text above allows us to name the base class because [class]/2

The class-name is also inserted into the scope of the class itself; this is known as the injected-class-name. For purposes of access checking, the injected-class-name is treated as if it were a public member name.

The above clearly says that starting a fully qualified name with A:: allows you to specify a member or a base class. Since A has no bases, you can only specify A::B (a "member type"). But A::B also nominates a class. So we may specify a base or member of that as well with A::B::, which allows us to name A::B::A. Now rinse and repeat.


1 - Note it's a completely other B. Not at all related to the global struct B.
2 - A recommended minimum of 256 according to [implimits]/2.36

Bulldozer answered 29/11, 2017 at 6:48 Comment(5)
What will happen if replace std::cout<<"Sizeof A::B::A::B::A::B::A::B::A::B is " << sizeof(A::B::A::B::A::B::A::B::A::B)<<std::endl; Names recursion ?Faerie
@Faerie - You aren't doing recursion really. You just backtrack and alternatively name one of two types. You only have two types in that expression, not matter how many time you repeat it. So sizeof( A::B::A::B::A::B::A::B::A::B) would be no different than sizeof(A::B)Bulldozer
@Faerie - hang on. I'll add a standard quote. We'll see if it clears it up for you.Bulldozer
@Faerie - There. That's the quote. Note how it says that if the "prefix" names a class. Doesn't matter how the class is named. One we name it, we may use :: again to access something in it.Bulldozer
Note to new coders A::B::A::B::A::B might get you hung or at best drawn and quartered. Naming things is hard, but it isn't THAT hard :-)Cashmere
A
21

First of all struct B; is a forward declaration of struct B in global namespace. It might be confusing because it is actually not relevant in this example. This global B can be accessed as ::B or as just B.

struct A {
    struct B;
};

Is a definition of struct A in global namespace with a forward declaration of nested struct B (not the same as previously declared B in global namespace). This nested B can be accessed as ::A::B or A::B.

struct A::B:A {
};

Is a definition of nested struct B of struct A that inherits from A (with access specifier omitted). It can be rewritten to:

struct A::B
:   public A
{
};

Note that writing definition of nested struct B inside of A definition like this won't work:

struct A {
    struct B: A { // error: A is incomplete at this point
    };
};

And finally A::B::A is referring to the base class of nested struct B, that is to A, so A::B::A::B is equivalent to just A::B.

Almeria answered 29/11, 2017 at 7:0 Comment(13)
To clarify (for myself and anyone else wondering) -- the default inheritance access modifier for struct is public, as opposed to class's private? (Hence saying that : public A is the equivalent)Rambow
@QPaysTaxes Yes, default access modifier for struct is public and it is applied to fields, methods, nested types and base classes. Note that this helps to maintain backwards compatibility with C, otherwise all the fields of C structs would be inaccessible.Almeria
What will happen if replace std::cout<<"Sizeof A::B::A::B::A::B::A::B::A::B is " << sizeof(A::B::A::B::A::B::A::B::A::B)<<std::endl; Recursion?Faerie
Public/Private/Protected inheritance does not play any role here. Here is a recursion inheritance, is not it?!Faerie
@Faerie A::B::A::B::A::B::A::B::A::B is not a recursion inheritance. It is just a chain of nested type access which is still equivalent to A::B. Also note that recursive inheritance is not possible in C++ because at some point one of the types at inheritance chain would be incomplete. And the given code contains no recursive inheritance.Almeria
@Faerie It is also possible to make this chain work without any inheritance at all: struct B; struct A{ using B = ::B; }; struct B{using A = ::A; };Almeria
Why is A::B::A::B::A::B::A::B::A::B equivalent to A::B?Faerie
@Faerie Because A::B::A is equivalent of just A. You can verify this by writing static_assert(::std::is_same_v<A, A::B::A>);. So this chain can be simplified from left to right like A::B::A::B::A::B::A::B then A::B::A::B::A::B then A::B::A::B then A::B.Almeria
Where is it in Standart? Why is it allowed to discard the scope name resolution?Faerie
@Faerie Nothing gets discarded, it is just equivalent forms of referring to the same type. No matter how many times A::B:: block is encountered it would be the same thing.Almeria
@VTT, many Thangs. But where is in the c++ standard is allowed this the equivalent and no matter how many times A::B:: block is encountered ?Faerie
@Faerie I don't think that standard puts some explicit restrictions of length of language constructs. Though real life compilers may put restrictions on symbol name length or will eventually run out of memory anyway.Almeria
@Faerie You're misunderstanding. The rules are right there in the standard; look for name resolution, namespaces, that kind of thing. You seem to think that ClassA::ClassB::ClassA is some special construct, and it's not. It's just getting the member of ClassA called ClassB, then getting that type's member called ClassA. Because of how we've defined things, that second ClassA happens to be exactly the same as the first.Rambow

© 2022 - 2024 — McMap. All rights reserved.