C++ Logo

std-discussion

Advanced search

Re: Why does overload resolution fail in this simple case?

From: jim x <xmh970252187_at_[hidden]>
Date: Wed, 30 Dec 2020 23:23:12 +0800
A more simpler example is:
#include <iostream>
struct A{
operator short(){
return 0;
}
operator float(){
return 0;
}
};
void fun(int&&){}
void fun(float){}
int main() {
fun(A{});
}
The result <https://godbolt.org/z/YTM43b> is ambiguous and is necessary.
Even if the second standard conversion sequence for `void fun(float)` is
better than that for `void fun(int&&)`, however, they have different
conversion functions. Hence, they are not comparable.

jim x <xmh970252187_at_[hidden]> 于2020年12月30日周三 下午11:11写道:

> Your suggestion is similar with [over.match.best#general-2.3]
> <https://eel.is/c++draft/over.match.best#general-2.3>. However, for your
> second example, I think it's necessary to be ambiguous. Compare two
> different user-defined conversions does not make sense, which is the intent
> of [over.ics.rank#3.3] <https://eel.is/c++draft/over.ics.rank#3.3>.
> Except for that, two user-defined conversion sequences are
> indistinguishable.
>
> Hani Deek via Std-Discussion <std-discussion_at_[hidden]>
> 于2020年12月30日周三 下午9:32写道:
>
>> Thanks jim x for clearing it up, and thanks to Lénárd Szolnoki for his
>> valuable contribution.
>> I do not claim to have great experience with the C++ standard, but I
>> humbly feel that this issue exposes a deficiency in 12.2.4.3
>> [over.ics.rank]. If two user-defined conversion sequences use two different
>> user-defined conversion functions, then I think it makes sense to rank as
>> better the user-defined conversion function whose ref-qualifier is a closer
>> match to the value category of the object being converted.
>>
>> In my example, I think it is logical to rank 'operator int &&() &&' as
>> better than 'operator const int &() const &', because the object that is
>> being converted is a prvalue.
>>
>> ------------------------------
>> For your first example, the parameter of the first function is of type
>> `int` and the corresponding argument has type `S`, Hence, the following
>> rules apply here:
>> > Otherwise, if the source type is a (possibly cv-qualified) class type,
>> conversion functions are considered. The applicable conversion functions
>> are enumerated (*[over.match.conv]*), and the best one is chosen through
>> overload resolution.
>> Through look at the rule in [over.match.conv], the candidate functions
>> are formed by:
>> > The conversion functions of S and its base classes are considered.
>> Those non-explicit conversion functions that are not hidden within S and *yield
>> type T or a type that can be converted to type T via a standard conversion
>> sequence* are candidate functions.
>>
>> Hence, `operator int() const &` and `operator char() &&` are all
>> candidate functions, in order to determine which is the best, hence the
>> overload resolution applies here to find out the best, in the first
>> candidate function, the implicit parameter object type is `S const&`.
>> Rather, the implicit parameter object type of the second candidate is
>> `S&&`. In this case, the argument is of type `S` and a prvalue. According
>> to:
>> [over.ics.rank#3.2.3]
>> >S1 and S2 are reference bindings ([dcl.init.ref]) and neither refers to
>> an implicit object parameter of a non-static member function declared
>> without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and
>> S2 binds an lvalue reference.
>>
>> `operator char() &&` wins the game.
>> And according to:
>> >The effect of any implicit conversion is the same as performing the
>> corresponding declaration and initialization and then using the temporary
>> variable as the result of the conversion. The result is an lvalue if T is
>> an lvalue reference type or an rvalue reference to function type
>> ([dcl.ref]), an xvalue if T is an rvalue reference to object type, and a
>> prvalue otherwise. The expression e is used as a glvalue if and only if the
>> initialization uses it as a glvalue.
>>
>> That means `char tmp = S{};` where the `tmp` is used as the argument in
>> function call. A similar process undergoes for `void foo(char)`. Now,
>> the first example can be simplified to when the argument is of type `char`,
>> which function is the best when calling foo(tmp). Certainly, it is `void
>> foo(char)`.
>>
>> Now, let us consider the second example.
>> Arguably, it is different from the first example. the parameter of `void
>> foo(const int &)` is of type `int const&`. So [dcl.init.ref] applies
>> here. According to [dcl.init.ref#5.1.2]
>> <https://timsong-cpp.github.io/cppwp/n4659/dcl.init.ref#5.1.2> and
>> [over.match.ref#1.1]
>> <https://timsong-cpp.github.io/cppwp/n4659/over.match.ref#1.1>, all
>> functions that have return type of lvalue reference to type T are candidate
>> functions, here only `operator const int &() const &` is. And from the
>> type of this conversion to `const int &` has an identity conversion.
>> Instead, for the second `void foo(int &&)`, the candidate conversion
>> function is only `operator int &&() &&` per [dcl.init.ref#5.2.1.2]
>> <https://timsong-cpp.github.io/cppwp/n4659/dcl.init.ref#5.2.1.2> and [over.match.ref#1.1].
>> From the result of `operator int &&() &&` to `int &&` is also an identity
>> conversion. Hence, your second example gives an ambiguous error.
>>
>>
>> Hani Deek via Std-Discussion <std-discussion_at_[hidden]>
>> 于2020年12月30日周三 上午5:05写道:
>>
>> Hello,
>>
>>
>> With MSVC, the following code compiles. The char overload is selected by
>> overload resolution.
>>
>> struct S
>> {
>> operator int() const & { return 0; }
>> operator char() && { return 0; }
>> };
>>
>> void foo(int) {}
>> void foo(char) {}
>>
>> int main()
>> {
>> foo(S{}); //OK, calls 'void foo(char)'.
>> }
>>
>> However, the following code won't compile.
>>
>> struct S
>> {
>> int i = 0;
>> operator const int &() const & { return i; }
>> operator int &&() && { return (int &&)(i); }
>> };
>>
>> void foo(const int &) {}
>> void foo(int &&) {}
>>
>> int main()
>> {
>> foo(S{}); //error C2668: 'foo': ambiguous call to overloaded function
>> //message : could be 'void foo(int &&)'
>> //message : or 'void foo(const int &)'
>> }
>>
>> What is exactly the difference that makes the second sample fail to
>> compile?
>>
>> Given that 'operator int &&() &&' is an exact match to the conversion
>> required to call 'void foo(int &&)', I expected the compiler to select
>> it. It is strange if the C++ rules will not allow the compiler to select a
>> conversion function that exactly matches the required conversion, both in
>> terms of the provided argument 'S &&' and the result of conversion 'int
>> &&'.
>>
>> --
>> Std-Discussion mailing list
>> Std-Discussion_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>>
>> --
>> Std-Discussion mailing list
>> Std-Discussion_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>>
>

Received on 2020-12-30 09:23:28