C++ Logo

std-discussion

Advanced search

Re: [over.ics.rank] standard conversion tie-breakers from different source types

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Wed, 11 Jan 2023 03:00:03 +0000
On Wed, 11 Jan 2023 at 02:12, Edward Catmur <ecatmur_at_[hidden]> wrote:

>
>
> On Tue, 10 Jan 2023 at 22:00, Lénárd Szolnoki via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
>
>> On Tue, 10 Jan 2023 17:47:38 +0000
>> Edward Catmur via Std-Discussion <std-discussion_at_[hidden]>
>> wrote:
>>
>> > On Tue, 10 Jan 2023 at 17:05, Brian Bi <bbi5291_at_[hidden]> wrote:
>> >
>> > >
>> > >
>> > > On Tue, Jan 10, 2023 at 11:24 AM Edward Catmur via Std-Discussion <
>> > > std-discussion_at_[hidden]> wrote:
>> > >
>> > >>
>> > >>
>> > >> On Tue, 10 Jan 2023 at 15:57, language.lawyer--- via
>> > >> Std-Discussion < std-discussion_at_[hidden]> wrote:
>> > >>
>> > >>> > I would like an example of a function call to an overloaded
>> > >>> > function, where the most viable function is determined by the
>> > >>> > rules listed from [over.ics.rank]/4.4.5 to
>> > >>> > [over.ics.rank]/4.4.8:
>> > >>> >
>> > >>> > https://timsong-cpp.github.io/cppwp/n4868/over.ics.rank#4.4.5
>> > >>> >
>> > >>> > These are four different tie-breakers for ranking standard
>> > >>> > conversions from different source types, assuming the following
>> > >>> > class hierarchy:
>> > >>> >
>> > >>> > struct A {}; struct B : C {}; struct C : B {};
>> > >>> >
>> > >>> > (4.4.5) conversion of B* to A* is better than conversion of C*
>> > >>> > to A*, (4.4.6) binding of an expression of type B to a
>> > >>> > reference to type A is better than binding an expression of
>> > >>> > type C to a reference to type A, (4.4.7) conversion of B::* to
>> > >>> > C::* is better than conversion of A::* to C::*, and
>> > >>> > (4.4.8) conversion of B to A is better than conversion of C to
>> > >>> > A.
>> > >>> >
>> > >>> > The note says that these are only used for tie-breakers in the
>> > >>> > second conversion sequence of user-defined conversions:
>> > >>> >
>> > >>> > https://timsong-cpp.github.io/cppwp/n4868/over.ics.rank#note-1
>> > >>>
>> > >>> I think it is just a wrong Note.
>> > >>> CD2 (November 96
>> > >>>
>> https://www.open-std.org/jtc1/sc22/wg21/docs/wp/html/cd2/over.html#over.ics.rank
>> )
>> > >>> Note was saying:
>> > >>> > [Note: it is necessary to compare conversions with different
>> > >>> > target types in the context of an initialization by
>> > >>> > user-defined conver- sion; see _over.match.best_. ]
>> > >>>
>> > >>> Which also seems not 100% correct (incomplete?), because it
>> > >>> mentions (only?) target, and not source types.
>> > >>>
>> > >>> The next WP (Oct'97
>> > >>>
>> https://www.open-std.org/jtc1/sc22/wg21/docs/wp/html/oct97/over.html#over.ics.rank
>> )
>> > >>> Note gained its current (defective) wording.
>> > >>>
>> > >>> (The bullets themselves have been added by
>> > >>> https://www.open-std.org/JTC1/sc22/wg21/docs/papers/1995/N0661.asc)
>> > >>>
>> > >>> So, ecatmur's example seems to be relevant here, except that the
>> > >>> Note is not about the second standard conversion sequence after
>> > >>> different user conversion functions, but about
>> > >>>
>> https://timsong-cpp.github.io/cppwp/n4868/over.match.best#general-2.2
>> > >>>
>> > >>
>> > >> Ah. So, currently, the Note says:
>> > >>
>> > >> > Compared conversion sequences will have different source types
>> > >> > only in
>> > >> the context of comparing the second standard conversion sequence
>> > >> of an initialization by user-defined conversion (see
>> > >> [over.match.best]); in all other contexts, the source types will
>> > >> be the same and the target types will be different.
>> > >>
>> > >> Whereas [over.match.best.general]/2.2 says:
>> > >>
>> > >> > ... the context is an initialization by user-defined conversion
>> > >> > (see
>> > >> [dcl.init], [over.match.conv], and [over.match.ref]) and the
>> > >> standard conversion sequence from the return type of F1 to the
>> > >> destination type (i.e., the type of the entity being initialized)
>> > >> is a better conversion sequence than the standard conversion
>> > >> sequence from the return type of F2 to the destination type ...
>> > >>
>> > >> Would it make sense to amend the latter to mention the second SCS?
>> > >> i.e.,
>> > >> > ... the context is an initialization by user-defined conversion
>> > >> > (see
>> > >> [dcl.init], [over.match.conv], and [over.match.ref]) and the
>> > >> <ins>second</ins> standard conversion sequence <ins>of ICSj(F1)
>> > >> (i.e., that</ins> from the return type of F1 to the
>> > >> <del>destination type (i.e.,</del> the type of the entity being
>> > >> initialized) is a better conversion sequence than the
>> > >> <ins>second</ins> standard conversion sequence <del>from the
>> > >> return type of F2 to the destination type</del><ins>of
>> > >> ICSj(F2)</ins> ...
>> > >
>> > > I think if we need to clarify it, we should just add a note.
>> > > Changing normative wording always risks unforeseen consequences.
>> > >
>> >
>> > Ah, Well, should we change the offending Note (to [over.ics.rank])
>> > then?
>> >
>> > > Compared conversion sequences will have different source types
>> > > only in
>> > the context of <del>comparing the second standard conversion sequence
>> > of</del> an initialization by user-defined conversion<ins>,
>> > specifically when comparing the standard conversion sequence from the
>> > return type of a viable function to the destination type</ins> (see
>> > [over.match.best]); in all other contexts, the source types will be
>> > the same and the target types will be different.
>>
>> I might have found a case where no user-defined conversions are
>> involved:
>>
>> struct A {
>> void foo();
>> };
>>
>> struct B : A {
>> using A::foo;
>> void foo(int);
>> };
>>
>> struct C : B {};
>>
>> void f(void (C::*)()); // 1
>> void f(void (C::*)(int)); // 2
>>
>> int main() {
>> // if conversion from void (B::*)(int) to void (C::*)(int)
>> // is better than void (A::*)() to void (C::*)()
>> // then the second overload must be called
>> f(&B::foo);
>> }
>>
>> I think https://eel.is/c++draft/over.ics.rank#4.5.7 applies, although
>> only gcc implements this.
>>
>> https://godbolt.org/z/7corz5539
>>
>> Anyway, if this applies, then the note wrongly states that these only
>> apply in the context of user-defined conversions.
>>
>
> Oh, brilliant. OTOH, where the pointer is used directly, there's full
> agreement:
>
> struct A { void foo(); };
> struct B : A { using A::foo; void foo(); };
> struct C : B {};
> void (C::*p())() { return &C::foo; } // B::foo
>
> But not if we write it like this; only MSVC accepts:
>
> struct A { void foo(); };
> struct B : A { void foo(); };
> struct C : B { using A::foo; using B::foo; };
> void (C::*p())() { return &C::foo; } // B::foo (MSVC only)
>
> Do you have any thoughts on this?
>
> And there's another issue with this, though;
> https://eel.is/c++draft/over.over#2 says:
>
> > ... a non-template function with type F is selected for the function
> type FT of the target type if F (after possibly applying the function
> pointer conversion ([conv.fctptr])) is identical to FT.
> [Note: That is, the class of which the function is a member is ignored
> when matching a pointer-to-member-function type. — end note]
>
> This Note is problematic; what is meant by "matching"? This is too early
> for overload resolution, but looking at the context in which it was added
> https://cplusplus.github.io/CWG/issues/1153.html it's clear (to me) that
> instead this should read "That is, the class of which the function is a
> member is ignored when <del>matching</del><ins>selecting functions
> for</ins> a pointer-to-member-function type.".
>
> Also, we may select member functions that cannot be converted to the
> target pointer to member function type:
>
> struct A { void foo(); };
> struct B : A { using A::foo; void foo(); };
> struct C : B {};
> struct D : C { using B::foo; void foo(); };
> void (C::*p())() { return &D::foo; }
>
> Unfortunately, everyone rejects this, but IMO this should be valid; it
> should remove D::foo from the overload set before ranking.
>

Also, where the offending overload is from a sibling class on the cadet
branch, EDG at least accepts if everyone else rejects:

struct A { void foo(); };
struct B { void foo(); };
struct C : A, B { using A::foo; using B::foo; }; // EDG also rejects
without these using-declarations
void (A::*p())() { return &C::foo; } // A::foo (EDG)

So I would have [over.over]/2 refer to [conv.mem] and amend the Note:
>
> > ... a non-template function is selected if the type of a pointer to the
> function can be converted to the target type via a standard conversion
> sequence consisting only of pointer-to-member conversions ([conv.mem]) and
> function pointer conversions ([conv.fctptr]). [Note: The function type of
> the target type must thus be identical to the function type of the
> function. --end note]
>
> In any case, we now may have a set of (pointer to member) functions that
> may be ranked according to [over.ics.rank]/4.5.7. But [over.over]/5
> doesn't tell us to do this, or indeed when to do this!
>
> If we give B::f unsatisfied constraints (as a function template
> specialization), everyone (gcc, clang and MSVC, that is - I don't have an
> EDG with concepts) is happy to select A::f:
>
> struct A {
> template<int...> int f();
> };
> struct B : A {
> using A::f;
> template<int...> int f() requires(false);
> };
> int (B::*p())() { return &B::f; }
>
> But if we make B::f a non-template function with unsatisfied constraints,
> MSVC rejects:
>
> template<int...> struct A {
> int f();
> };
> template<int...> struct B : A<> {
> using A<>::f;
> int f() requires(false);
> };
> int (B<>::*p())() { return &B<>::f; }
>
> If we make B::f a function template specialization and A::f a non-template
> function, everyone except EDG prefers A::f:
>
> struct A {
> int f();
> };
> struct B : A {
> using A::f;
> template<int...> int f();
> };
> int (B::*p())() { return &B::f; } // A::f (gcc, clang, MSVC), B::f<> (EDG)
>
> If we make A::f and B::f function template specializations with A::f more
> constrained than B::f, gcc, clang and MSVC prefer A::f:
>
> struct A {
> template<int...> int f() requires(true);
> };
> struct B : A {
> using A::f;
> template<int...> int f();
> };
> int (B::*p())() { return &B::f; } // A::f<> (gcc, clang, MSVC)
>
> But if we make A::f and B::f non-template functions with A::f more
> constrained than B::f, gcc and clang error while MSVC prefers B<>::f:
>
> template<int...> struct A {
> int f() requires(true);
> };
> template<int...> struct B : A<> {
> using A<>::f;
> int f();
> };
> int (B<>::*p())() { return &B<>::f; } // &A<>::f (more constrained) or
> &B<>::f (better pointer-to-member)?
>
> For consistency with [over.match.best.general]/2, which
> puts [over.ics.rank] (at 2.1) ahead of [temp.constr.order] (at 2.6), I
> would select B::f in all cases and amend [over.over]/5:
>
> > All functions with associated constraints that are not satisfied
> ([temp.constr.decl]) are eliminated from the set of selected functions.
> <ins>Any given member function MF1 is eliminated if the set contains a
> second member function MF2 such that the standard conversion sequence from
> the type of a pointer to MF2 to the target type is better than the standard
> conversion sequence from the type of a pointer to MF1 to the target type.
> [Note: when ranking such standard conversion sequences, only
> pointer-to-member conversions ([conv.mem]) are considered. --end
> note]</ins> If more than one function in the set remains ...
>
> Wow. So now we have a further fix to the Note to [over.ics.rank]):
>
> > Compared conversion sequences will have different source types only in
> the context of <del>comparing the second standard conversion sequence
> of</del> an initialization by user-defined conversion<ins>, specifically
> when comparing the standard conversion sequence from the return type of a
> viable function to the destination type</ins> (see [over.match.best])
> <ins>or of taking the address of an overload set consisting of member
> functions ([over.over])</ins>; in all other contexts, the source types will
> be the same and the target types will be different.
>
> And [over.ics.rank]/3.3 still has the dubious wording about aggregate
> initialization.
>

Received on 2023-01-11 03:00:17