8

I noticed by accident that this code compiles and works correctly:

struct M { int some_int; };
static_assert(std::is_same<
                   decltype(M::M::M::M::some_int) /* <- this */,
                   int>::value, "Types must be int");

Why is this correct (decltype(M::M::M::M::some_int) <=> decltype(M::some_int))?

What other constructs one can use this pattern with class::class::...::member?

Compiler: Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23824.1 for x86

0xFF
  • 4,140
  • 7
  • 41
  • 58

2 Answers2

10

This is valid in all contexts, not just decltype. A class contains its own name as the injected class name. So within a class A::B::M, the name M is injected to refer to the class A::B::M. This means that you can then use M::M::M::some_member to refer to members of that class, if you really want to.

[Live example]

Note that when referring just to the class name itself (e.g. M::M::M), the situation is slightly different. If such a reference occurs in a place where a reference to a function could also potentially be correct, the syntax is taken to refer to the constructor instead. However, in type-only contexts, even such reference is valid. Example:

M::M::M m;  // illegal, M::M interpreted as reference to constructor

struct D : public M::M::M  // legal, a function could not be references here, so M::M::M means M
{};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
9

This works because of the injected-class-name:

(N3337) [class]/2:A class-name is inserted into the scope in which it is declared immediately after the class-name is seen. 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. [...]

So you can arbitrarily nest these, and they'll work with derived types as well:

struct A { using type = int; };
struct B : public A {};

using foo = B::B::B::A::A::A::type;

Note that in the case of A[::A]*::A, the injected-class-name can be considered to name the constructor instead:

[class.qual]/2: In a lookup in which the constructor is an acceptable lookup result and the nested-name-specifier nominates a class C:

— if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C (Clause 9), or

— [...]

the name is instead considered to name the constructor of class C.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
  • 2
    *"Note that in the case of `A[::A]*::A`, the injected-class-name can be considered to name the constructor instead:"* Ib4 the natural follow-up dupe: Yes, clang tends to get this wrong. See e.g. [this](https://stackoverflow.com/questions/29681449/program-being-compiled-differently-in-3-major-c-compilers-which-one-is-right). – Baum mit Augen Jun 06 '16 at 08:04