This question can be considered probably theoretical as I don't see why I would have such design in real code. I've got two classes with some conversion operators:
class B;
class A {
public:
A() = default;
A(B const &);
};
class B {
public:
B() = default;
operator A();
};
A::A(B const &) {};
B::operator A() { return A{}; };
int main() {
[[maybe_unused]] A ad{B{}}; // direct init
[[maybe_unused]] A ac = B{}; // copy init
}
With gcc only and with copy initialization only I've got this warning:
warning: choosing 'B::operator A()' over 'A::A(const B&)' [-Wconversion]
warning: for conversion from 'B' to 'A' [-Wconversion]
note: because conversion sequence for the argument is better
Not being well-versed in the rules of conversion, I do not understand if this warning is relevant? If not, how can it be silenced (it can be painful with -Werror
)?
NB Adding a bit of traces, I observed different behaviors according to compilers and the initialization nature. Are conversion sequences implementation-dependent to some extent?
(Not a 100% confident answer, but I'm not sure one is possible.)
Part of the complexity here is that your operator A()
is not a const member function. So we have a choice between a conversion function taking a non-const this
, and a converting constructor taking a const this
(which is a slightly worse match in this case because your input B
prvalue is non-const).
struct A {
A(const B&) { puts("A(const B&)"); }
};
struct B {
operator A() { puts("B::operator A()"); return A(); }
};
A f(B b) {
return A(b);
// Clang in >= C++17 mode: B::operator A()
// Everyone else: A(const B&)
}
A g(B b) {
return b;
// MSVC in <= C++17 mode: A(const B&)
// Everyone else: B::operator A()
}
If you make it B::operator A() const
, then most compilers call return b
ambiguous (Godbolt), while still permitting return A(b)
.
struct A {
A(const B&) { puts("A(const B&)"); }
};
struct B {
operator A() const { puts("B::operator A()"); return A(); }
};
A f(B b) {
return A(b);
// Everyone: A(const B&)
}
A g(B b) {
return b;
// GCC: A(const B&)
// MSVC in <= C++17 mode: A(const B&)
// Everyone else: Ambiguous
}
This is basically the topic of CWG 2327; see also Preference of conversion operator over copy constructor changes from C++14 to C++17? and Call to conversion operator instead of converting constructor in c++17 during overload resolution .
As for why Clang prefers the conversion function over the converting constructor, I'm not 100% sure but it seems like Clang bug #89501 could be related. It sounds like the current behavior just kinda happened, not necessarily by design; but they're not planning to touch it until CWG 2327 gets resolved.