Date: Fri, 7 Apr 2023 17:37:40 -0400
On Fri, Apr 7, 2023 at 3:37 PM Jan Schultke via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> No, I don't have a working implementation, but here is an example that
> demonstrates the benefits:
> https://www.godbolt.org/z/Ejn9KK57x
>
> When the polymorphic class is already de-virtualized, then returning
> Base& breaks the de-virtualization in the second and third call. With
> a "this" return type, we would have the guarantee that the dynamic
> type stays the same, so we can keep making direct calls.
>
> The only exception to this is when a virtual function ends and begins
> the lifetime of "this" before returning, though I'm not sure if this
> is really allowed by the standard, and could also be declared UB for
> this return types, if need be.
>
IIUC, your example intends that the `this` keyword would be used both in
`Base` and also in `B`, is that right?
https://www.godbolt.org/z/xWdcaoxE6
I think Lénárd might have been thinking that the `this` keyword would be
used only in `B` but not in `Base`.
There are a lot of semantic question-marks to be filled in here:
(1) In this program, what is decltype(Derived().f()) — is it automatically
the covariant type `Derived&`, or is it the same `Base&` as in the parent
class?
struct Base { virtual this f(); };
struct Derived { this f() override; } // What is the return type of
this method?
(2) Is it allowed for a derived class to use `this` where its parent class
doesn't? Again, what is decltype(Derived().f()) in this case?
struct Base { virtual Base& f(); };
struct Derived { this f() override; } // OK, or error?
(3) Is it allowed for a derived class to *omit* `this` where its parent
class has it? I assume not. But one could imagine its working like
`virtual`, and being implied if the parent class has it.
struct Base { virtual this f(); };
struct Derived { Derived& f() override; } // OK, or error?
(4) Is it simply UB for such a function to return anything but `*this`?
Does the compiler enforce this at all?
struct S { this f(bool b) { static S s; if (b) return s; } }; //
UB-if-taken, or IFNDR, or hard error, or what?
Plus a syntactic question-mark or two:
(5) Is this in trailing position allowed?
struct S { auto f() -> this; };
(6) Is this part of the function's type? Presumably not.
using MFP1 = Base& (Base::*)(); // OK
using MFP2 = this (Base::*)(); // Pretty sure this *must* be a syntax
error; the real-estate is already taken
Finally, if you do pursue an implementation of this (I'm not saying you
should look to standardize it — the syntactic space is already *insanely*
crowded, and C++23 makes it worse), it would naturally lend itself to an
extension of P2266 implicit move. See:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1155r1.html#further
(7) The idea is:
struct X { X& operator=(const X&); };
struct Y { this operator=(const Y&); };
X f() { X x; return x = X(); }
Y g() { Y y; return y = Y(); }
In the former case we must treat `x = X()` as an lvalue expression and
copy-construct from `x` into the return slot.
In the latter case, we *know* that `y = Y()` refers to the same object as
`y`, and so we can (a) treat it as an lvalue, and (b) optionally subject it
to NRVO/copy-elision.
A proposal should engage with this issue somehow — either propose to do the
efficient thing now that it's implementable, or else explain why you're
*not* proposing the efficient thing.
(8) I like(*) the proposed streamlining of
A& operator=(A a) { swap(*this, a); return *this; }
A& operator++() { ++member_; return *this; }
into
this operator=(A a) { swap(*this, a); }
this operator++() { ++member_; }
but I do vaguely worry that it might lead to a spate of people writing
something like
this operator++(int) { member_++; }
which of course isn't going to work. Probably not a big deal, though,
because the "edit distance" between the dumb thing I just wrote and the
actually correct `operator++(int)` is pretty far — I don't currently see
any really *plausible* way for someone to get all the way to hitting
"Compile" without realizing that what they wrote is silly.
(* — However, if I wanted to streamline the implementation of copy-and-swap
`operator=`, I wouldn't pick that point to stop. That's a sour spot: we've
made the syntax obscure but we still have to write too many tokens
to actually get the job done. Meanwhile the syntax for a defaulted
*memberwise* `operator=` is already optimal: `=default`.)
my $.02,
–Arthur
std-proposals_at_[hidden]> wrote:
> No, I don't have a working implementation, but here is an example that
> demonstrates the benefits:
> https://www.godbolt.org/z/Ejn9KK57x
>
> When the polymorphic class is already de-virtualized, then returning
> Base& breaks the de-virtualization in the second and third call. With
> a "this" return type, we would have the guarantee that the dynamic
> type stays the same, so we can keep making direct calls.
>
> The only exception to this is when a virtual function ends and begins
> the lifetime of "this" before returning, though I'm not sure if this
> is really allowed by the standard, and could also be declared UB for
> this return types, if need be.
>
IIUC, your example intends that the `this` keyword would be used both in
`Base` and also in `B`, is that right?
https://www.godbolt.org/z/xWdcaoxE6
I think Lénárd might have been thinking that the `this` keyword would be
used only in `B` but not in `Base`.
There are a lot of semantic question-marks to be filled in here:
(1) In this program, what is decltype(Derived().f()) — is it automatically
the covariant type `Derived&`, or is it the same `Base&` as in the parent
class?
struct Base { virtual this f(); };
struct Derived { this f() override; } // What is the return type of
this method?
(2) Is it allowed for a derived class to use `this` where its parent class
doesn't? Again, what is decltype(Derived().f()) in this case?
struct Base { virtual Base& f(); };
struct Derived { this f() override; } // OK, or error?
(3) Is it allowed for a derived class to *omit* `this` where its parent
class has it? I assume not. But one could imagine its working like
`virtual`, and being implied if the parent class has it.
struct Base { virtual this f(); };
struct Derived { Derived& f() override; } // OK, or error?
(4) Is it simply UB for such a function to return anything but `*this`?
Does the compiler enforce this at all?
struct S { this f(bool b) { static S s; if (b) return s; } }; //
UB-if-taken, or IFNDR, or hard error, or what?
Plus a syntactic question-mark or two:
(5) Is this in trailing position allowed?
struct S { auto f() -> this; };
(6) Is this part of the function's type? Presumably not.
using MFP1 = Base& (Base::*)(); // OK
using MFP2 = this (Base::*)(); // Pretty sure this *must* be a syntax
error; the real-estate is already taken
Finally, if you do pursue an implementation of this (I'm not saying you
should look to standardize it — the syntactic space is already *insanely*
crowded, and C++23 makes it worse), it would naturally lend itself to an
extension of P2266 implicit move. See:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1155r1.html#further
(7) The idea is:
struct X { X& operator=(const X&); };
struct Y { this operator=(const Y&); };
X f() { X x; return x = X(); }
Y g() { Y y; return y = Y(); }
In the former case we must treat `x = X()` as an lvalue expression and
copy-construct from `x` into the return slot.
In the latter case, we *know* that `y = Y()` refers to the same object as
`y`, and so we can (a) treat it as an lvalue, and (b) optionally subject it
to NRVO/copy-elision.
A proposal should engage with this issue somehow — either propose to do the
efficient thing now that it's implementable, or else explain why you're
*not* proposing the efficient thing.
(8) I like(*) the proposed streamlining of
A& operator=(A a) { swap(*this, a); return *this; }
A& operator++() { ++member_; return *this; }
into
this operator=(A a) { swap(*this, a); }
this operator++() { ++member_; }
but I do vaguely worry that it might lead to a spate of people writing
something like
this operator++(int) { member_++; }
which of course isn't going to work. Probably not a big deal, though,
because the "edit distance" between the dumb thing I just wrote and the
actually correct `operator++(int)` is pretty far — I don't currently see
any really *plausible* way for someone to get all the way to hitting
"Compile" without realizing that what they wrote is silly.
(* — However, if I wanted to streamline the implementation of copy-and-swap
`operator=`, I wouldn't pick that point to stop. That's a sour spot: we've
made the syntax obscure but we still have to write too many tokens
to actually get the job done. Meanwhile the syntax for a defaulted
*memberwise* `operator=` is already optimal: `=default`.)
my $.02,
–Arthur
Received on 2023-04-07 21:37:54