Consider the following two classes :
#define PRETTY(x) (std::cout << __PRETTY_FUNCTION__ << " : " << (x) << '\n')
struct D;
struct C {
C() { PRETTY(this);}
C(const C&) { PRETTY(this);}
C(const D&) { PRETTY(this);}
};
struct D {
D() { PRETTY(this);}
operator C() { PRETTY(this); return C();}
};
We are interested in the overload resolution between the two constructors :
C::C(const C&);
C::C(const D&);
This code work as expected :
void f(const C& c){ PRETTY(&c);}
void f(const D& d){ PRETTY(&d);}
/*--------*/
D d;
f(d); //calls void f(const D& d)
since void f(const D& d) is a better match.
But :
D d;
C c(d);
(as you can see here)
calls D::operator C() when compiling with std=c++17, and calls C::C(const D&) with std=c++14 on both clang and gcc HEAD.
Moreover this behaviour has changed recently :
with clang 5, C::C(const D&) is called with both std=c++17 and std=c++14.
What is the correct behaviour here ?
Tentative reading of the standard (latest draft N4687) :
C c(d) is a direct-initialization which is not a copy elision ([dcl.init]/17.6.1). [dcl.init]/17.6.2 tells us that applicable constructors are enumerated and that the best one is chosen by overload resolution. [over.match.ctor] tells us that the applicable constructors are in this case all the constructors.
In this case : C(), C(const C&) and C(const D&) (no move ctor). C() is clearly not viable and thus is discarded from the overload set. ([over.match.viable])
Constructors have no implicit object parameter and so C(const C&) and C(const D&) both take exactly one parameter. ([over.match.funcs]/2)
We now go to [over.match.best]. Here we find that we need to determine which
of these two implicit conversion sequences (ICS) is better. The ICS of
C(const D&) only involves a standard conversion sequence, but the ICS of C(const C&) involves a user-defined conversion sequence.
Therefore C(const D&) should be selected instead of C(const C&).
Interestingly, these two modifications both causes the "right" constructor to be called :
operator C() { /* */ } into operator C() const { /* */ }
or
C(const D&) { /* */ } into C(D&) { /* */ }
This is what would happen (I think) in the copy-initialization case where user-defined conversions and converting constructors are subject to overload resolution.
As Columbo recommends I filed a bug report with gcc and clang