always stores exactly a T. But what if you need to store a Circle or a Rectangle through a Shape base class — and copy it without slicing? That’s std::polymorphic, the other half of P3019R14. It owns a heap-allocated object whose dynamic type may be T or any type derived from T. Copying performs a type-erased deep copy that preserves the dynamic type. No virtual clone() needed." /> always stores exactly a T. But what if you need to store a Circle or a Rectangle through a Shape base class — and copy it without slicing? That’s std::polymorphic, the other half of P3019R14. It owns a heap-allocated object whose dynamic type may be T or any type derived from T. Copying performs a type-erased deep copy that preserves the dynamic type. No virtual clone() needed." /> C++26: std::polymorphic | Sandor Dargo's Blog<br>Sandor Dargo's Blog<br>On C++, software development and books
HOME TAGS ARCHIVES BOOKS SPEAKING DAILY C++ WORKSHOPS HI... SUBSCRIBE
Blog 2026 08 19 C++26: std::polymorphic Post<br>Cancel
C++26: std::polymorphic<br>Sandor Dargo Aug 19 2026-08-19T00:00:00+02:00<br>6 min
In the previous article, we looked at std::indirect — C++26’s answer to the “I need a heap-allocated member with value semantics” problem. indirect always stores exactly a T. But what if you need to store a Circle or a Rectangle through a Shape base class — and copy it without slicing?<br>That’s std::polymorphic, the other half of P3019R14. It owns a heap-allocated object whose dynamic type may be T or any type derived from T. Copying performs a type-erased deep copy that preserves the dynamic type. No virtual clone() needed.<br>The clone() tax<br>To have a copyable collection of polymorphic objects, every derived class needs a virtual clone() method. Let’s see what that looks like:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>class Shape {<br>public:<br>virtual ~Shape() = default;<br>virtual std::unique_ptrShape> clone() const = 0;<br>virtual double area() const = 0;<br>};
class Circle : public Shape {<br>double radius_;<br>public:<br>explicit Circle(double r) : radius_(r) {}<br>std::unique_ptrShape> clone() const override {<br>return std::make_uniqueCircle>(*this);<br>double area() const override { return 3.14159 * radius_ * radius_; }<br>};
class Rectangle : public Shape {<br>double w_, h_;<br>public:<br>Rectangle(double w, double h) : w_(w), h_(h) {}<br>std::unique_ptrShape> clone() const override {<br>return std::make_uniqueRectangle>(*this);<br>double area() const override { return w_ * h_; }<br>};
Now you want a Picture that owns a collection of shapes with value semantics:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>// full example at https://godbolt.org/z/Evz8b7r8o<br>class Picture {<br>std::vectorstd::unique_ptrShape>> shapes_;<br>public:<br>Picture(const Picture& other) {<br>shapes_.reserve(other.shapes_.size());<br>for (const auto& s : other.shapes_)<br>shapes_.push_back(s->clone());<br>Picture& operator=(const Picture& other) {<br>auto tmp = other; // copy-and-swap<br>swap(shapes_, tmp.shapes_);<br>return *this;<br>// ... move constructor, move assignment, destructor<br>};
Every new shape has to implement clone(). Forget it once and you get slicing. The Picture class needs all five special member functions written by hand. This is a lot of ceremony for something conceptually simple: “I want a copyable collection of polymorphic objects.”<br>Dropping the boilerplate with std::polymorphic<br>std::polymorphic eliminates all of that. The shapes don’t need clone(), Picture doesn’t need any special member functions, and — perhaps surprisingly — Shape doesn’t even need a virtual destructor:<br>10<br>11<br>12<br>13<br>14<br>15<br>16<br>17<br>18<br>19<br>20<br>21<br>22<br>23<br>24<br>25<br>26<br>27<br>28<br>29<br>30<br>31<br>32<br>33<br>34<br>35<br>36<br>37<br>38<br>39<br>40<br>41<br>42<br>43<br>44<br>45<br>#include
class Shape {<br>protected:<br>~Shape() = default; // no virtual destructor needed!<br>public:<br>virtual double area() const = 0;<br>};
class Circle : public Shape {<br>double radius_;<br>public:<br>explicit Circle(double r) : radius_(r) {}<br>double area() const override { return 3.14159 * radius_ * radius_; }<br>};
class Rectangle : public Shape {<br>double w_, h_;<br>public:<br>Rectangle(double w, double h) : w_(w), h_(h) {}<br>double area() const override { return w_ * h_; }<br>};
class Picture {<br>std::vectorstd::polymorphicShape>> shapes_;<br>public:<br>void add_circle(double r) {<br>shapes_.emplace_back(std::in_place_typeCircle>, r);<br>void add_rectangle(double w, double h) {<br>shapes_.emplace_back(std::in_place_typeRectangle>, w, h);
double total_area() const {<br>double sum = 0;<br>for (const auto& s : shapes_)<br>sum += s->area();<br>return sum;
size_t size() const { return shapes_.size(); }
// ALL special member functions are compiler-generated.<br>// Copying deep-copies every shape, preserving its dynamic type.<br>};
No clone(). No Rule of Five. Copying a Picture copies every shape — a Circle is copied as a Circle, a Rectangle as a Rectangle. The type-erasure machinery inside polymorphic handles this automatically. The only requirement is that every stored type must be copy-constructible.<br>Deep copies just work<br>10<br>Picture a;<br>a.add_circle(5.0);<br>a.add_rectangle(3.0, 4.0);
Picture b = a; // deep copies both shapes, preserving...