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 ?