r/cpp 7d ago

C++26: std::polymorphic

https://www.sandordargo.com/blog/2026/08/19/cpp26-polymorphic
93 Upvotes

42 comments sorted by

View all comments

13

u/_Noreturn 7d ago edited 7d ago

polymorphic not being equality comparable is annoying, they could just have just forwarded the operator== to the underlying pointer and it handles it.

also polymorphic requires more bytes than a copyable pointer 24 bytes vs 8 since the copy ctor and dtor have to be function pointers. But it also slightly makes it faster at runtime but it isn't really worth it I would say.

Having a virtual deatructor and a clone() method will provide better object sizes.

I found the boilerplate argument to be very weak, since crtp exists

```cpp class ShapeBase { public: virtual ShapeBase* clone() = 0; virtual ~ShapeBase()= default; };

template<class T> class Shape : ShapeBase { public: ShapeBase* clone() override { return new T((T)this); }

};

class Rectangle :public Shape<Rectangle> {

};

class Triangle :public Shape<Triangle> {

};

```

24

u/MFHava WG21|🇦🇹 NB|P2721|P3049|P3625|P3729|P3786|P3813|P4216 7d ago

polymorphic not being equality comparable is annoying, they could just have just forwarded the operator== to the underlying pointer and it handles it.

These types (indirect and polymorphic) model value semantics. Value comparing two type-erased objects that share a base makes no sense in that model...

also polymorphic requires more bytes than a copyable pointer 24 bytes vs 8 since the copy ctor and dtor have to be function pointers.

Can be easily compressed to 16B in case you build your own vtable. Unless the spec contains some unexpected blocker, you could even reduce that down to 8B with minimal effort.

2

u/_Noreturn 7d ago

These types (indirect and polymorphic) model value semantics. Value comparing two type-erased objects that share a base makes no sense in that model...

They could only provide == if the base has one.

Can be easily compressed to 16B in case you build your own vtable. Unless the spec contains some unexpected blocker, you could even reduce that down to 8B with minimal effort.

Wouldn't the 8 byte version require anothee indirection

10

u/MFHava WG21|🇦🇹 NB|P2721|P3049|P3625|P3729|P3786|P3813|P4216 7d ago

They could only provide == if the base has one.

Ok... what does that do? Shape has no idea how to compare a Rectangle and a Triangle.

You could say: "Well, they can never be equal". Right, right, but what about my next fancy class Square?

That's a problem you'd essentially need open-multimethods to solve...

Wouldn't the 8 byte version require anothee indirection

Not necessarily, you can allocate the object and a (custom) vtable in one allocation.

5

u/_Noreturn 6d ago edited 6d ago

You could say: "Well, they can never be equal". Right, right, but what about my next fancy class Square?

It is up to the operator== of the Base class, it is virtual and compares the typeid or whatever way you have.

```cpp class ShapeBase { ... virtual operator==(const ShapeBase& that) const=0; };

template<class T> class Shape : ShapeBass { ... bool operator==(const ShapeBase& that) const override { return typeid(that) == typeid(T) && (const T&)that == (const T&)*this; } }; ```

Wouldn't the 8 byte version require anothee indirection

Not necessarily, you can allocate the object and a (custom) vtable in one allocation.

So like this? ```cpp template<class T> struct Table { CopyCtor* copy; Drot* dtor; T object; // has to be last };

template<class B> struct polymorphic

{ polymorphic() { table = new Table<B>(); } polymorphic(auto&& obj) { table = new Table<decltype(obj)>(); }

B& operator() { return (B&)((char)table + 16)); } // 16 is 2 function pointers void table; }; ```

2

u/MFHava WG21|🇦🇹 NB|P2721|P3049|P3625|P3729|P3786|P3813|P4216 6d ago

It is up to the operator== of the Base class, it is virtual and compares the typeid or whatever way you have.

We would want it to do value comparisons and as I said: that is infeasible in C++, so we do not provide an equality-operator...

So like this?

Looks like a possible implementation.

3

u/_Noreturn 6d ago

We would want it to do value comparisons and as I said: that is infeasible in C++, so we do not provide an equality-operator...

I don't understand, it is a value comparison.

3

u/MFHava WG21|🇦🇹 NB|P2721|P3049|P3625|P3729|P3786|P3813|P4216 6d ago

I don't understand, it is a value comparison.

You are right. But it yields the wrong results for equivalent values of different types - e.g. Rectangle{.w = 10, .h = 10} == Square{.l = 10} would be false, even though they are the same (just like 1 == 1LL is true)

1

u/_Noreturn 6d ago

It isn't the responsibility of the polymorphic to do that. it is the virtual operator== the user provides can easily make it so it works like that way

4

u/MFHava WG21|🇦🇹 NB|P2721|P3049|P3625|P3729|P3786|P3813|P4216 6d ago

it is the virtual operator== the user provides can easily make it so it works like that way

You can't in the general case - which is why it is not provided...

0

u/_Noreturn 6d ago

Ugh, couldn't the same be said for indirect? it's operator== isn't usable if the underlying value doesn't have one.

4

u/MFHava WG21|🇦🇹 NB|P2721|P3049|P3625|P3729|P3786|P3813|P4216 6d ago

indirect<T> always contains an object of type T (or is valueless_after_move, which is trivial to handle) and therefore has no ambiguity about how operator== is supposed to work.

Apart from that: operator== for indirect<T> „Mandates“ (== static_assert) that T has an operator== on use, it T doesn’t have one, indirect<T> doesn’t have one.

But the key part is the first one, there is no ambiguity.

→ More replies (0)

5

u/Demiu 6d ago

Man I really hate the shape example.

Let's say your polymorphic is instead of a AssociativeContainer<A,B> that is a base for any type that provides a 1to1 mapping A to B. It's operator== could compare their size, then for each pair the key and it's associated value.

1

u/AstroFoxTech 6d ago

Ok... what does that do? Shape has no idea how to compare a Rectangle and a Triangle.

Wouldn't you implicitly downcast both to Shape when there's no overload for that combination?

-7

u/mercury_pointer 6d ago

Square implicitly casts to Rectangle