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