C++ Logo

std-discussion

Advanced search

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

From: Lénárd Szolnoki <cpp_at_[hidden]>
Date: Wed, 30 Dec 2020 16:18:16 +0000
Hi,

On Wed, 30 Dec 2020 13:48:17 +0800
jim x via Std-Discussion <std-discussion_at_[hidden]> wrote:

> 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.

In my reading `operator int&&() &&` should also be a candidate, as
rvalue returning conversion functions are also candidates. In case it's
the only candidate it seems to be selected just fine:

https://godbolt.org/z/8Wo6se

However if the other conversion function is uncommented then that one
is deemed better by all compilers, even though I would expect the
rvalue ref qualified one to be better according to
[over.ics.rank#3.2.3], similarly to the first example.

> 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
> >

Received on 2020-12-30 10:18:24