Search code examples
c++type-conversionthisc++23explicit-object-parameter

Does Explicit Object Parameter Allow Convertible Types?


From §4.2.7 of the proposal http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0847r7.html#pathological-cases

It said that:

These are even more unlikely to be actually useful code. In this example, B is neither convertible to A nor int, so neither of these functions is even invocable using normal member syntax. However, you could take a pointer to such functions and invoke them through that pointer. (&B::bar)(42) is valid, if weird, call.

However, It does not specify whether the standard does not allow explicit object parameter of type-of-self implicitly convertible to particular another type.

struct A { };
struct B {
    void foo(this A&);
    void bar(this int);
};

Does that mean that:

struct A { };
struct B {
  operator A() const noexcept;
  void foo(this A);
};

// ...
// '&B::foo' is of type function pointer not pointer to member function
// because it uses explicit object parameter.
(&B::foo)(A{}); 
(&B::foo)(B{});
B{}.foo(); // will work?

will work?

In another case, here is a lambda. Since the type of the lambda is unutterable and is always dependent. What about the case above? (this captureless lambda is convertible to int(*)(int, int, int))

auto fib = [](this int(* self)(int, int, int), int n, int a = 0, int b = 1) {
  return n == 0 ? a : n == 1 ? b : self(n - 1, b, a + b);
};

Given that:

Non-member functions, static member functions, and explicit object member functions match targets of function pointer type or reference to function type. Non-static Implicit object member functions match targets of pointer-to-member-function type. ([over.match.viable] §12.2.3)

In all contexts, when converting to the implicit object parameter or when converting to the left operand of an assignment operation only standard conversion sequences are allowed. [Note: When converting to the explicit object parameter, if any, user-defined conversion sequences are allowed. - end note ] ([over.best.ics] §12.2.4.2)


Solution

  • For your first question:

    struct A { };
    struct B {
      operator A() const noexcept;
      void foo(this A);
    };
    
    B{}.foo(); // will work?
    

    Yes. Candidate lookup will find B::foo, which more or less evaluates as foo(B{}), which is valid due to the conversion function. This is explicitly called out in the note you cited, in [over.ics.best]/7:

    [Note 5: When converting to the explicit object parameter, if any, user-defined conversion sequences are allowed. — end note]


    This one, I'm actually not entirely sure about:

    auto fib = [](this int(* self)(int, int, int), int n, int a = 0, int b = 1) {
      return n == 0 ? a : n == 1 ? b : self(n - 1, b, a + b);
    };
    

    It seems exceedingly unlikely to be useful and you should probably never do this, so I don't know that it matters whether or not it's actually valid. But also am not sure what it means for examples like this:

    struct C {
        C(auto);
        void f();
    };
    
    auto lambda = [](this C c) { c.f(); }; // OK?
    

    If this is convertible to a function pointer, what function pointer type exactly? If it's void(*)(), then which C are we invoking f() on? So it'd kind of have to be void(*)(C), in which case the fib example definitely does not work because it's impossible to spell the function pointer type non-generically in a way that matches.