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 02:12:14 +0000
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. 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 02:12:27