C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Polymorphic operator new and polymorphic values

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Wed, 30 Apr 2025 12:23:54 -0400
On Wed, Apr 30, 2025 at 12:00 PM Hans Åberg <haberg_1_at_[hidden]> wrote:
>
>
> > On 30 Apr 2025, at 17:55, Jason McKesson via Std-Proposals <std-proposals_at_[hidden]> wrote:
> >
> > On Wed, Apr 30, 2025 at 11:47 AM Hans Åberg via Std-Proposals
> > <std-proposals_at_[hidden]> wrote:
> >>
> >>
> >>> On 30 Apr 2025, at 15:55, Ville Voutilainen <ville.voutilainen_at_[hidden]> wrote:
> >>>
> >>> On Wed, 30 Apr 2025 at 16:33, Hans Åberg via Std-Proposals
> >>> <std-proposals_at_[hidden]> wrote:
> >>>>> I don't even know what polymorphic operator new means, so this doesn't
> >>>>> seem like a very common scenario. Who would benefit from it being in
> >>>>> the standard?
> >>>>
> >>>> The polymorphic operator new is also called a clone operator or a virtual copy operator, given such names rather than “new”. As I also use it for move, I call them polymorphic operator new. As “new” is a reserved word, it cannot be used without additions to the language.
> >>>>
> >>>>>> As for the val<A> type, I think the GC references are generally used just as a hack for polymorphic values:
> >>>>>>
> >>>>>> One can use reference counting, like in std::shared_ptr, or say the Boehm GC. But then one will have to remember when to copy an object, and there one gets errors in the implementation.
> >>>>>
> >>>>> I'm still not sure what you're actually proposing for the standard.
> >>>>
> >>>> Suppose:
> >>>> class A {
> >>>> virtual A* new_p(void* p) const& { return new A(*this); }
> >>>> };
> >>>>
> >>>> class B : A {
> >>>> virtual B* new_p(void* p) const& { return new B(*this); }
> >>>> };
> >>>>
> >>>> class C : A {
> >>>> // No new_p
> >>>> };
> >>>>
> >>>> Then:
> >>>> A* bp = new B, cp = new C;
> >>>> bp->new_p(); // Gets a copy of *bp
> >>>> cp->new_p(); // Gets a copy of A(*cp)
> >>>> In the last copy, the object gets truncated to A, not the wanted full C object.
> >>>>
> >>>> So one must add by hand new_p to every new class created that is derived from A.
> >>>>
> >>>> An addition might be that one can write
> >>>> class A {
> >>>> virtual A* new_p(void* p) const& = default;
> >>>> virtual A* new_p(void* p) && = default;
> >>>> };
> >>>> with the implication that every derived class D of A, as well as A, gets
> >>>> class D : A
> >>>> virtual D* new_p(void* p) const& { return new D(*this); }
> >>>> virtual D* new_p(void* p) && { return new D(std::move(*this)); }
> >>>> };
> >>>
> >>> So, use https://eel.is/c++draft/polymorphic
> >>
> >> The primary issue is to get proper copy and move of derived classes. On top of that, one can think of different interfaces.
> >>
> >> There I keep the allocation and the val<A> type together. So I have
> >> // Placeholder struct and value
> >> struct make_t {};
> >> constexpr make_t make{};
> >>
> >> val<A> av(make, …); // Applies new A(…)
> >> rather than
> >> A* ap = new A(…);
> >> val<A> av(ap;
> >>
> >> I cannot ditch the “make” argument, as there will be conflicts with the class val<A> constructors.
> >
> > I don't see how this is relevant. The point is that `std::polymorphic`
> > is able to do the things you *need* done, even if they're not done the
> > specific way you *want* it to be done.
> >
> > Your problem is that you have an `A*`, and you want to copy it, but
> > you want the copy to be of the proper derived class that is behind the
> > `A*`. The copy constructor of `polymorpic<A>` is able to use the copy
> > constructor of the derived class stored, even though it only has `A`
> > in its name.
> >
> > You can say that `polymorphic` is more cumbersome than a bespoke
> > solution, but it solves your problem in its entirety.
>
> Can you give an example of how it is supposed to work?

It's pretty simple and straightforward.

You gave the example of:

```
class A {
  virtual A* new_p(void* p) const& { return new A(*this); }
};

class B : A {
  virtual B* new_p(void* p) const& { return new B(*this); }
};

class C : A {
  // No new_p
};

A* bp = new B, cp = new C;
bp->new_p(); // Gets a copy of *bp
cp->new_p(); // Gets a copy of A(*cp)

```

So, using `std::polymorphic`, none of those classes would explicitly
need a virtual function to invoke copying. So it would look like:

```
class A
{
public:
  virtual ~A(){}
};

class B : A
{
  /*stuff*/
};

class C : A
{
  /*stuff*/
};

std::polymorphic<A> pb{std::in_place_type_t<B>, /*arguments to B()*/};
std::polymorphic<A> pc{std::in_place_type_t<C>, /*arguments to C()*/};

auto copyb = pb; //invokes copy constructor of `B`.
auto copyc = pc; //invokes copy constructor of `C`.
```

And if you want to do memory allocation/deallocation yourself instead
of relying on `operator new`, `std::polymorphic` not only provides it,
but it can impose that allocator on any type. That is, `A`, `B`, and
`C` don't get a say in how allocation is done. Which is better than
your proposal, since yours binds the means of allocation to the type
itself.

Received on 2025-04-30 16:24:06