C++ Logo

std-discussion

Advanced search

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

From: Hani Deek <hani_deek_at_[hidden]>
Date: Wed, 30 Dec 2020 13:31:59 +0000
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]<mailto: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_lists.isocpp.org<mailto:Std-Discussion_at_[hidden]>
https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion

Received on 2020-12-30 07:32:05