12

Given

struct E
{
};

struct P
{
    explicit P(E) {}
};

struct L
{
    operator E() {return {};}
    operator P() {return P{E{}};}
};

According to the C++17 language standard, should the expression P{L{}} compile?

Different compilers produce different results:

  • gcc (trunk): ok
  • gcc 8.3: error (overload ambiguous)
  • gcc 7.4: ok
  • clang (trunk): ok
  • clang 8.0.0: ok
  • clang 7.0.0: ok
  • msvc v19.20: error (overload ambiguous)
  • icc 19.0.1: error (more than one constructor instance matches)
precarious
  • 598
  • 2
  • 14
  • 3
    While this is an interesting question in itself, I'm more interested in *why* you ask this? What is the actual problem you have that lead you to ask this? – Some programmer dude Apr 30 '19 at 18:53
  • @Someprogrammerdude Some of my code stopped to compile when I upgraded from gcc 7.4 to 8.3. I know how to work around it but I want to understand if the code is correct due to the standard. All mentioned versions of gcc and clang do not compile the code in C++14 mode. – precarious Apr 30 '19 at 19:12

1 Answers1

4

I think the correct behavior per the standard is to be ambiguous.

[dcl.init]/17.1:

If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized.

[dcl.init.list]/3.6:

Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.

And [over.match.list] just talks about picking a constructor. We have two viable options: P(E) by way of L{}.operator E() and P(P&&) (the implicit move constructor) by way of L{}.operator P(). None is better the other.


However, this is very reminiscent of CWG 2327:

struct Cat {};
struct Dog { operator Cat(); };

Dog d;
Cat c(d);

Which as the issue indicates currently invokes Cat(Cat&&) instead of just d.operator Cat() and suggests that we should actually consider the conversion functions as well. But it's still an open issue. I'm not sure what gcc or clang did in response to this issue (or in response to similar examples that got brought up first), but based on your results I suspect that they decide that the direct conversion function L{}.operator P() is a better match and just do that.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I'm glad my gut wasn't all wrong. It seems as if all recent versions of the major compilers skip the move constructor in the example from [CWG 2327](https://wg21.link/cwg2327). I had to go back to Visual Studio 2015 (msvc v19.0) to witness the issue. – precarious Apr 30 '19 at 21:14