vendredi 5 janvier 2018

odd C++ namespace resolution quirk and g++ vs clang++

This began with an observation. I changed some code that looked a bit like this:

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

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

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

Note that file2.cc excessively-qualifies E2. Yet this works in both g++ and clang++. And indeed, we can write:

S v3 = { .member = 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 ?

Aucun commentaire:

Enregistrer un commentaire