0

Why was the last A picked from ns1 namespace instead of ns2?
(Bar is of type ns1::A<ns1::A<ns2::A, ns2::B>, ns2::B>)

namespace ns1 {
  template <typename T, typename U>
  struct A {};
}

namespace ns2 {
  struct /*ns2*/ A : ns1::A<int, /*ns2*/ A> {
    typedef ns1::A<int, /*ns2*/ A> Foo;
  };
  struct B : ns1::A< /*ns2*/ A, B> {
    typedef ns1::A< /*ns1*/ A, B> Bar;
  };
}

I'm using vscode with g++ 11.2.0

Jason
  • 36,170
  • 5
  • 26
  • 60
  • @JasonLiam from [IDE](https://postimg.cc/1gQYg63W) and from compilation bugs – Pootis Spencer Oct 23 '22 at 17:26
  • @JasonLiam it is not a [type](https://postimg.cc/dhKzCgWm) of Bar, it is type of A – Pootis Spencer Oct 23 '22 at 17:39
  • If the namespace is omited [sic], the compiler will search the global namespace. If there are more namespaces with the `using` statement, those will be searched also. – Thomas Matthews Oct 23 '22 at 17:42
  • You're right, [here](https://godbolt.org/z/dcTdzjoME) is an example that prints the type of `Bar` without relying on an IDE - it prints `ns1::A, ns2::B>` with both GCC and clang. – interjay Oct 23 '22 at 17:45
  • 2
    The reason is that the `A` refers to the base class of `ns2::B` (it's allowed not to specify the template arguments when referring to the base class). – interjay Oct 23 '22 at 17:46
  • 2
    See: https://stackoverflow.com/questions/4277477/is-a-namespace-required-when-referring-to-the-base-class – interjay Oct 23 '22 at 17:53

2 Answers2

2

C++ classes have an injected class name available in class scope, which refers to the class itself. For example, for ns1::A<T, U>, the name A refers to ns1::A<T, U> in class scope .

This injected class name can also be accessed in derived classes, so in ns2::B, the name A refers to the base class which is ns1::A<ns2::A, ns2::B>.

The injected class name behaves like a member typedef, so it takes precedence over namespace-scope names when in class scope. To refer to a namespace-scope type you'd need to qualify it.

Within ns2::A, you get different behavior: here the base class has an injected class name A referring to ns1::A<int, ns2::A> (similarly to before), but ns2::A also has an injected class name A referring to itself. The derived class's names take precedence so A refers to ns2::A here.

interjay
  • 107,303
  • 21
  • 270
  • 254
0

From unqualified names:

For a name used anywhere in class definition (including base class specifiers and nested class definitions), except inside a member function body, a default argument of a member function, exception specification of a member function, or default member initializer, where the member may belong to a nested class whose definition is in the body of the enclosing class, the following scopes are searched:

  • the body of the class in which the name is used until the point of use

(emphasis mine)

Now let us apply this to the two cases.

Case 1

Here we consider:

namespace ns2 {
//-------------------v---------->#1  
  struct B : ns1::A< A, B> {
//------------------v----------->#2
    typedef ns1::A< A, B> Bar;
  };
}

In this case, the unqualified name A at point #1 is looked up and according to bullet point 1 quoted above, it refers to ns2::A.

Now, the base class of B is ns1::A< ns2::A, ns2::B> and this base class has an injected-class name A which refers to the current instantiation ns1::A< ns2::A, ns2::B>. Moreover, it is as-if this base class has a public member named A referring to ns1::A< ns2::A, ns2::B>.

This means that the derived class B behaves as-if it has an inherited member named A from the base class.

Now, the A at point #2 is looked up and since this derived class B has inherited a member named A(same as ns1::A) from the base class, the bullet point 1 founds this ns1::A(which itself refers to ns1::A< ns2::A, ns2::B>). This also explains why in your posted image the type of A at point #2 is ns1::A< ns2::A, ns2::B>.

Case 2

Here we consider:

namespace ns2 {
//-------------------------------v------> #1
  struct /*ns2*/ A : ns1::A<int, A> {
//----------------------v---------------> #2
    typedef ns1::A<int, A> Foo;
  };
 
}

At point #1, according to bullet point 1, A is refers to ns2::A just like in case 1 discussed before.

But this time the inherited member named A from base class ns1::A<int, ns2::A> is hidden by a member with the same name. This is because the class ns2::A itself has an injected-class name A so it is as-if it has a member named A which will hide the inherited member with the same name from the base.

This in turn means that the A at point #2 refers to ns2::A(unlike in case 1).

Jason
  • 36,170
  • 5
  • 26
  • 60