5

This began with an observation. I changed some code that looked a bit like this (edit: I took out the designated initializers here, which weren't in the original code either):

struct S {
    enum E { E1, E2 } member;
}

// file1.cc
S v1 = { S::E1 };

// file2.cc
S v2 = { S::S::E2 };

Note that file2.cc excessively-qualifies E2. Yet this works in both g++ and clang++. (Edit 2: the g++ on this particular VM is g++-5.4.1, but the original code's been through earlier and later g++ versions, plus multiple clang versions.) And indeed, we can write:

S v3 = { S::S::S::S::S::S::S::E1 };

(however many S::s we like), wherever we like. I changed things so that S was no longer a plain struct, but rather a templated one, after which this stopped working. Not that big a deal but it got me curious, so I experimented.

If we change this to a non-POD type:

struct S {
    S() { std::cout << "made an S" << std::endl; }
    enum E { E1, E2 } member;
}

(with appropriate #include) it's no longer allowed. Clang and g++ produce different diagnostics. Here's clang's complaint:

namespace.cc:8:3: error: no matching constructor for initialization of 'S'
S x = { .member = S::S::E1 };
namespace.cc:3:8: note: candidate constructor (the implicit copy constructor)
      not viable: cannot convert argument of incomplete type 'void' to
      'const S &' for 1st argument
struct S {
       ^
namespace.cc:3:8: note: candidate constructor (the implicit move constructor)
      not viable: cannot convert argument of incomplete type 'void' to 'S &&'
      for 1st argument
struct S {
       ^
namespace.cc:4:3: note: candidate constructor not viable: requires 0 arguments,
      but 1 was provided
  S() { std::cout << "made an S\n"; }
  ^
1 error generated.

and g++'s:

namespace.cc:8:28: error: could not convert ‘{E1}’ from ‘<brace-enclosed initializer list>’ to ‘S’
 S x = { .member = S::S::E1 };

These seem to be following different rules. What's going on here?

Next, let's try another bit of abuse. Here's the entire program:

#include <iostream>

struct S {
  S() { std::cout << "made an S\n"; }
  enum E { E1, E2 } member;
};

int main() {
  std::cout << S::S::S::S::S::E1 << std::endl;
#ifdef DECL
  S::S::S var;
#endif
  return 0;
}

This code compiles (without -DDECL) in both compilers:

$ clang++-3.9 -std=c++11 -Wall -O namespace.cc
$ ./a.out
0
$ g++ -Wall -std=c++11 -O namespace.cc
$ ./a.out
0

(No S is constructed here despite the complaint clang emits for the variable member initializer in the earlier code.) Enabling the variable in main, though, results in a failure with g++, but not with clang:

$ clang++-3.9 -std=c++11 -DDECL -Wall -O namespace.cc
$ ./a.out 
0
made an S
$ g++ -std=c++11 -DDECL -Wall -O namespace.cc
namespace.cc: In function ‘int main()’:
namespace.cc:11:3: error: ‘S::S’ names the constructor, not the type
   S::S::S var;
   ^
namespace.cc:11:11: error: expected ‘;’ before ‘var’
   S::S::S var;
           ^
namespace.cc:11:14: error: statement cannot resolve address of overloaded function
   S::S::S var;
              ^

Which compiler is right, and why? What exactly are the rules for this "overqualified" name ?

torek
  • 448,244
  • 59
  • 642
  • 775
  • 2
    Note [Designated Initializers are a C99 feature but it seems like clang and gcc support them in C++ as an extension](https://stackoverflow.com/a/19272351/1708801) – Shafik Yaghmour Jan 06 '18 at 01:16
  • @Jarod42: well, take out the `.member =` part; the same still applies. – torek Jan 06 '18 at 01:17
  • 1
    They’re also a C++20 feature, finally, but it’s only 2018 so they aren’t standard. – Daniel H Jan 06 '18 at 01:18
  • @ShafikYaghmour: OK, I took them out - they're just an irrelevant distraction to the actual question (which is, "what rule, if any, allows this kind of overqualification, and when?") – torek Jan 06 '18 at 01:20
  • This should be a dupe. Old clang bug not knowing when the `S::S::S` names the constructor, and when it names the class. – Baum mit Augen Jan 06 '18 at 01:20
  • @BaummitAugen: would appreciate finding the duplicate - I did search! – torek Jan 06 '18 at 01:21
  • Alright, so the old-bug explains why for `S::S::S` clang emits error about constructor/class ambiguty, However "bug in clang" this does not explain why in the first place the `S v3 = { S::S::S::S::S::S::S::E1 };` was successfully compiled ***both by clang and also g++***. Furthermore, clang did not report error here so for this case this bug seems to not trigger. Furthermore, there is no S::S::S::S class/namespace, and if we assume one of them to be the ctor, it's even more invalid – quetzalcoatl Jan 06 '18 at 01:23
  • I knew I should have bookmarked that. XD Gimme a few minutes. Maybe @quetzalcoatl comment makes the dupes non-applicaple anyways. We'll see. – Baum mit Augen Jan 06 '18 at 01:23
  • @quetzalcoatl Concerning *"There is no S::S::S::S class"* though: The class' name is injected into the class' scope, so thus far, that code is not nonsense at all. – Baum mit Augen Jan 06 '18 at 01:25
  • This is close: https://stackoverflow.com/a/32006973/3002139 – Baum mit Augen Jan 06 '18 at 01:29
  • @BaummitAugen That self-name injection.. makes some sense if to look at it this way.. unintuitive and insane, but it might be it... – quetzalcoatl Jan 06 '18 at 01:31
  • This is also close (disclaimer: I'm the author of the question): https://stackoverflow.com/questions/46412754/class-name-injection-and-constructors – Alex Huszagh Jan 06 '18 at 01:32
  • 1
    Note that the above mentioned clang bug was supposedly fixed in clang 5, and indeed clang 5 does not reproduce the discrepancy. https://wandbox.org/permlink/cwYV0XhkzouaHBxf – Baum mit Augen Jan 06 '18 at 01:32
  • Well, while the questions are not literally the same, the accepted answer of the Q I linked does solve everything I'd say. OP, what do you think? Anything still unclear, or should I hammer? – Baum mit Augen Jan 06 '18 at 01:37
  • 1
    Btw, *"If we change this to a non-POD type:"* You disabled aggregate initialization there, so you actually need a matching constructor. – Baum mit Augen Jan 06 '18 at 01:47
  • 3
    "what rule, if any, allows this kind of overqualification, and when?" - Clause 9, `[class]`, paragraph 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." - C++03 – Robᵩ Jan 06 '18 at 01:54
  • @BaummitAugen: yeah, it seems to be a manifestation of the same bug. (Sorry, was on the road...) – torek Jan 06 '18 at 03:03

2 Answers2

3

Designated initializers are a C feature coming to C++ in . They are a common extension in C++ mode. Clearly they are restricted to pod (C-like) types at this point. No surprises they break.

In general, different compilers can produce different errors for incorrect code, especially when working with extensions to C++. The C++ standard never mandates the content of a diagnostic (there may be an exception, but I do not recall what).

gcc has an extension that lets you name object constructors. This is conflicting with your pathological use of struct namespaces.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
3

Yakk already addresses the designated initializer part of your question. I'll address the last part of your question. Is S::S::S var (within this context) valid? No, per class.qual#2:

In a lookup in which function names are not ignored34 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 [class]), or

  • in a using-declarator of a using-declaration that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id's template-name in the last component of the nested-name-specifier,

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

In order to make it valid, you need to explicitly say struct S::S::S var. So clang 3.9 is wrong.

Also, Rob's comment is not relevant here. S::S is only an injected class name when looked up inside the class definition.