On Mon, 9 Jan 2023 at 00:13, Phil <std-discussion@chilippso.me> wrote:
Are you saying that an implicit conversion sequence from int to void* does not exist? Or that it does exist but is not viable in this case?

As far as I know, there can’t be no non-existing conversion sequence, but a sequence can happen to be empty, i.e. without any conversions.

That being said, there is specified an „implicit null pointer conversion“ from a „null pointer constant“, which is either an „integer literal with value 0“ or a „prvalue of type std::nullptr_t“.

If the evaluated argument is not a „null pointer constant“, there is no „implicit null pointer conversion“ and therefore it could happen that the conversion sequence for that argument is empty and therefore unable to convert.

According to [over.match.viable]/4, a function F is only considered viable, if (despite other requirements) there exists for each argument an implicit conversion sequence **that converts that argument to the corresponding parameter of F**.

So, given `foo(1, 1)`, there is no implicit conversion sequence that converts literal 1 to void* -> that overload is a candidate but not even viable at all.

That reads to me the same as saying that an implicit conversion sequence from literal 1 to void* does not exist.

Thus an implicit conversion sequence from int to void* does exist when that int is literal 0, but not otherwise. Is that correct?

If so, we have a conflict with the original mentioned paragraph of [over.best.ics.general].

at what point in the process is [conv.ptr]/1 considered?

At least by [over.match.viable] and [over.match.best] (and generally by many others regarding different topics).

We are specifically discussing [over.best.ics.general], so [over.match.best] is the process you should be referencing.
 
I'm fine with that. What follows from that ambiguity?

The call is ill-formed.

Not quite. It forms an ambiguous conversion sequence, which has a detectable difference to an ill-formed call; for example, it is better than an ellipsis conversion sequence:

struct U { short x : 1; };
template<class T, bool B>
struct S {
    struct X {
        X(void*);
        X(T);
    };
    int f(X);
    int f(...) requires (B);
    struct Y {
        Y(int&);
    };
    int g(Y);
    int g(...) requires (B);
};
template<class T, bool B>
concept C = requires(S<T, B> s) { s.f(0); };
template<class T, bool B>
concept D = requires(S<T, B> s) { s.f(1); };
template<class T, bool B>
concept E = requires(S<T, B> s, U u) { s.g(u.x); };
int ans[] = {
    C<short, false>, C<short, true>,
    D<short, false>, D<short, true>,
    E<short, false>, E<short, true>,
};