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;
}
ViRuSTriNiTy
  • 5,017
  • 2
  • 32
  • 58
devsh
  • 549
  • 1
  • 4
  • 7

2 Answers2

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

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 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)< – devsh Nov 29 '17 at 10:55
  • 4
    @devsh - 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)` – StoryTeller - Unslander Monica Nov 29 '17 at 11:00
  • @devsh - hang on. I'll add a standard quote. We'll see if it clears it up for you. – StoryTeller - Unslander Monica Nov 29 '17 at 11:04
  • @devsh - 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. – StoryTeller - Unslander Monica Nov 29 '17 at 11:10
  • 9
    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 :-) – boatcoder Nov 29 '17 at 18:04
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.

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • 1
    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) – Nic Nov 29 '17 at 09:10
  • 1
    @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. – user7860670 Nov 29 '17 at 09:16
  • 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)< – devsh Nov 29 '17 at 10:53
  • Public/Private/Protected inheritance does not play any role here. Here is a recursion inheritance, is not it?! – devsh Nov 29 '17 at 11:00
  • 1
    @devsh `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. – user7860670 Nov 29 '17 at 11:05
  • 1
    @devsh 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; };` – user7860670 Nov 29 '17 at 11:12
  • Why is A::B::A::B::A::B::A::B::A::B equivalent to A::B? – devsh Nov 29 '17 at 11:25
  • 2
    @devsh Because `A::B::A` is equivalent of just `A`. You can verify this by writing `static_assert(::std::is_same_v);`. 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`. – user7860670 Nov 29 '17 at 11:36
  • Where is it in Standart? Why is it allowed to discard the scope name resolution? – devsh Nov 29 '17 at 11:41
  • 1
    @devsh 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. – user7860670 Nov 29 '17 at 11:45
  • @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 ? – devsh Nov 29 '17 at 11:50
  • 1
    @devsh 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. – user7860670 Nov 29 '17 at 12:02
  • 1
    @devsh 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. – Nic Nov 29 '17 at 18:12