Reputation: 580
I am a bit stumped by the following conversion problem in C++11. Given this code:
#include <utility>
struct State {
State(State const& state) = default;
State(State&& state) = default;
State() = default;
int x;
};
template<typename T>
struct Wrapper {
T x;
Wrapper() = default;
operator T const&() const& { return x; }
// version which also works with GCC 4.9.2:
// operator T&&() && { return std::move(x); }
// version which does not work with GCC 4.9.2:
operator T() && { return std::move(x); }
};
int main() {
Wrapper<State> x;
State y(std::move(x));
}
godbolt link to the failed compilation with Clang
In the form above, g++ starting at version 5.1 and ICPC version 16 and 17 compile the code. If I uncomment the T&&
conversion operator and comment-in the currently-used second one:
operator T&&() && { return std::move(x); }
// version which does not work with GCC 4.9.2:
// operator T() && { return std::move(x); }
then GCC 4.9 also compiles. Otherwise, it complains:
foo.cpp:23:23: error: call of overloaded ‘State(std::remove_reference<Wrapper<State>&>::type)’ is ambiguous
State y(std::move(x));
^
foo.cpp:23:23: note: candidates are:
foo.cpp:5:3: note: constexpr State::State(State&&)
State(State&& state) = default;
^
foo.cpp:4:3: note: constexpr State::State(const State&)
State(State const& state) = default;
However, clang never compiles the code, equally complaining about an ambiguous call to the constructor of State
.
This, I do not understand. Given the std::move(x)
, I would expect to have an rvalue of type Wrapper<State>
. Then, shouldn’t the conversion operator T&&() &&
be clearly better than the T const&() const&
one? And given that, shouldn’t the rvalue-reference constructor of State
be used to construct y
from the rvalue-reference return value of the conversion?
Can someone explain the ambiguity to me and ideally also whether Clang or GCC (and if so, in which version) is right and what would be the best way to achieve the move out of the wrapper into the state object?
Upvotes: 6
Views: 252
Reputation: 119562
It's useful to note that recent versions of Clang and GCC no longer disagree with each other. In C++11 mode, the overload resolution is ambiguous. Godbolt link
There are two candidates for the initialization of State
: the copy constructor, and the move constructor.
In order to call the copy constructor, the compiler would need to find an implicit conversion sequence from std::move(x)
(xvalue of type Wrapper<State>
) to const State&
. When initializing a reference from a class type, a conversion function of that class that returns a compatible reference type takes precedence over a conversion function that returns a temporary that the reference can bind to. See C++11 [dcl.init.ref]/5 (emphasis mine):
A reference to type "cv1
T1
" is initialized by an expression of type "cv2T2
" as follows:
If the reference is an lvalue reference and the initializer expression
- is an lvalue (but is not a bit-field), and "cv1
T1
" is reference-compatible with "cv2T2
," or- has a class type (i.e.,
T2
is a class type), whereT1
is not reference-related toT2
, and can be implicitly converted to an lvalue of type "cv3T3
," where "cv1T1
" is reference-compatible with "cv3T3
" (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)),then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [...]
Otherwise, [...]
So the implicit conversion sequence for the copy constructor is to call operator T const&
and bind the State const&
parameter to the result of that call.
For the move constructor the only possibility is to call operator T
.
The question then becomes which of these two implicit conversion sequences is better. Call operator T const&
and then bind State const&
to the result of that call? Or call operator T
and then bind State&&
to the result of that call?
The rule for comparing two user-defined conversion sequences is taken from ([over.ics.rank]/3):
User-defined conversion sequence
U1
is a better conversion sequence than another user-defined conversion sequenceU2
if they contain the same user-defined conversion function or constructor or aggregate initialization and the second standard conversion sequence ofU1
is better than the second standard conversion sequence ofU2
.
However, in this case, two different user-defined conversion functions are involved (operator T const&
versus operator T
) so the two user-defined conversion sequences are incomparable. The overload resolution is indeed ambiguous, just as Clang 16 and GCC 12.2 say (in C++11 mode).
And note that Clang and GCC also agree that the code is still ambiguous (in C++11 mode) when you have operator T&&
instead of operator T
. In that case, when determining the implicit conversion sequence for the State
move constructor, the State&&
parameter can only bind to the result of calling operator T&&
. This is still a different conversion function than the one that the State const&
parameter uses, so the two implicit conversion sequences involved are still incomparable.
When you go to C++17 mode, things get interesting. Recent versions of Clang and GCC will both prefer to call operator T
in that case. This is because they have special overload resolution rules that are not actually in the standard. For an explanation of this behaviour, see P2828R0. However, if the direction proposed in P2828R0 is accepted, then this code will still be ambiguous (and perhaps Clang and GCC will have to change their behaviour), so I would recommend not relying on it.
I suppose what you might really want is to make it so that the const &
qualified conversion function will just never get picked when the object expression can be bound to an rvalue reference. I don't know of any way to do that in current C++, but you can do it in C++23 using explicit object parameters:
template <typename Self>
operator T const& (this Self&& self)
requires (!std::convertible_to<Self&&, Wrapper&&>) {
return self.x;
}
Upvotes: 2