On Wed, 11 Jan 2023 at 02:12, Edward Catmur <ecatmur@googlemail.com> wrote:


On Tue, 10 Jan 2023 at 22:00, Lénárd Szolnoki via Std-Discussion <std-discussion@lists.isocpp.org> wrote:
On Tue, 10 Jan 2023 17:47:38 +0000
Edward Catmur via Std-Discussion <std-discussion@lists.isocpp.org>
wrote:

> On Tue, 10 Jan 2023 at 17:05, Brian Bi <bbi5291@gmail.com> wrote:
>
> >
> >
> > On Tue, Jan 10, 2023 at 11:24 AM Edward Catmur via Std-Discussion <
> > std-discussion@lists.isocpp.org> wrote:
> > 
> >>
> >>
> >> On Tue, 10 Jan 2023 at 15:57, language.lawyer--- via
> >> Std-Discussion < std-discussion@lists.isocpp.org> 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.