std::polymorphic is a smart-wrapper class template in <memory> introduced in C++26 (standardized via proposal P3019, evolving from P0201 polymorphic_value) designed to provide deep-copy value semantics for dynamically allocated class hierarchies. Prior to C++26, runtime polymorphism in C++ forced software architectures into reference/pointer semantics (std::unique_ptr<Base> or std::shared_ptr<Base>), making value-like operations—such as copying containers of polymorphic objects—impossible without extensive hand-written clone() boilerplate across every derived class.
std::polymorphic<T> owns a heap-allocated instance of a type derived from T, automatically preserves the dynamic concrete type on copy operations via type-erased cloning machinery, enforces strict const-propagation (a const std::polymorphic<T> yields a const T&), prevents object slicing, and provides explicit lifecycle mechanics including move ownership transfer and a valueless_after_move() query.
Prior to C++26, C++ developers seeking to store derived objects in composite structures or standard containers faced a fundamental dilemma.
// Pre-C++26 Dilemma: Storing Polymorphic Objects in Containers
struct Shape { virtual ~Shape() = default; virtual void draw() const = 0; };
struct Circle : Shape { void draw() const override {} };
struct Square : Shape { void draw() const override {} };
// FLAW 1: Storing values directly causes Object Slicing!
// std::vector<Shape> shapes; // Fails to compile (abstract class) or slices derived state!
// FLAW 2: std::unique_ptr disables Container Copying!
std::vector<std::unique_ptr<Shape>> shapes1;
shapes1.push_back(std::make_unique<Circle>());
// std::vector<std::unique_ptr<Shape>> shapes2 = shapes1; // ERROR: std::unique_ptr is non-copyable!
// FLAW 3: std::shared_ptr breaks Value Semantics!
std::vector<std::shared_ptr<Shape>> shared1;
shared1.push_back(std::make_shared<Circle>());
auto shared2 = shared1; // Copying shares references! Mutating shared2[0] mutates shared1[0]!- Object Slicing: Direct value containment (
Shape s = Circle{}) truncates the derived portion of the object, stripping away member variables and dynamic virtual dispatch. - Non-Copyable Unique Containers: Storing
std::unique_ptr<Base>prevents container copy-construction. Duplicating astd::vector<std::unique_ptr<Shape>>required writing custom iteration loops and demanding that every derived class implement a customvirtual std::unique_ptr<Base> clone() constmember function. - Reference Aliasing Leaks: Using
std::shared_ptr<Base>enables container copying, but introduces shared ownership. Modifying a shape in the copied vector inadvertently modifies the shape in the original vector, violating value isolation. - Const Correctness Leaks: Accessing pointees through
const std::unique_ptr<T>allows non-const method invocation onT(ptr->non_const_method()), breaking logical constness propagation.
Solution: ISO C++26 introduced std::polymorphic<T> (P3019) to grant heap-allocated class hierarchies full, copyable value semantics while preserving runtime polymorphism.
1. Pointer Semantics (std::unique_ptr<Base>) 2. Value Semantics (std::polymorphic<Base>)
┌──────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────┐
│ unique_ptr<Base> │ │ polymorphic<Base> │
│ ┌───────────┐ ┌──────────────────────────┐ │ │ ┌───────────┐ ┌──────────────────────────┐ │
│ │ Control ├────────►│ Heap Object (Derived) │ │ │ │ Control ├────────►│ Heap Object (Derived) │ │
│ └───────────┘ └──────────────────────────┘ │ │ └───────────┘ └──────────────────────────┘ │
│ • Copying disabled (move-only) │ │ • Deep copy on value assignment/copy construction │
│ • Non-const pointer allows const-modifications │ │ • Strict const propagation (const T& on deref) │
└──────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────┘
- Type-Erased Deep Cloning: When a
std::polymorphic<Base>object is created with a derived instance (Circle), its constructor captures the operational function pointers required to copy-construct and destroy that concrete dynamic type. On copy construction or assignment,std::polymorphicallocates new heap memory and clones the concrete derived instance without requiringvirtual clone()methods. - Deep Value Independence: Copying a
std::polymorphic<Base>yields an entirely separate heap allocation. Modifying the copy never affects the original instance.
| Property | std::unique_ptr<T> |
std::shared_ptr<T> |
std::polymorphic<T> |
|---|---|---|---|
| Ownership Model | Exclusive Ownership | Shared Ownership | Exclusive Value Ownership |
| Copy Semantics | Disabled (delete) |
Shallow Copy (Reference Shared) | Deep Copy (Clones Concrete Type) |
const Propagation |
No (const ptr allows mutable *ptr) |
No (const ptr allows mutable *ptr) |
Yes (const ptr yields const T&) |
| Nullability State | Default nullptr |
Default nullptr |
Valueless state only after move |
| Sibling Type | N/A | N/A | std::indirect<T> (for Pimpl/non-poly types) |
-
std::polymorphic<T>is designed as a value type, not a nullable pointer. It does not have a defaultnullptrstate. - Moving a
std::polymorphic<T>transfers ownership of the allocated heap pointer to the target instance in$\mathcal{O}(1)$ time without allocation. - The moved-from object enters a valueless state. Querying
.valueless_after_move()returnstrue. Dereferencing a valuelessstd::polymorphicinstance results in undefined behavior.
┌────────────────────────────────────────────────────────────────────────┐
│ C++11 / C++14 / C++17 / C++20 / C++23 │
├────────────────────────────────────────────────────────────────────────┤
│ • Polymorphism required raw pointers, unique_ptr, or shared_ptr. │
│ • Container duplication required writing manual virtual clone() methods. │
│ • Const-correctness was lost across pointer indirection boundaries. │
└────────────────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────────────┐
│ C++26 │
├────────────────────────────────────────────────────────────────────────┤
│ • Introduced std::polymorphic<T> in <memory> (P3019). │
│ • Standardized deep-copy value semantics for class hierarchies. │
│ • Introduced std::indirect<T> as a sibling type for non-polymorphic │
│ Pimpl idioms and recursive value trees. │
│ • Enforced const-propagation and defined valueless_after_move(). │
└────────────────────────────────────────────────────────────────────────┘
Demonstrates storing polymorphic objects (Circle, Rectangle) inside a standard vector, duplicating the vector via value-copying, and illustrating const-propagation.
#include <iostream>
#include <vector>
#include <memory>
#include <string_view>
// C++26 feature check for std::polymorphic
#if defined(__cpp_lib_polymorphic) && __cpp_lib_polymorphic >= 202406L
class GraphicElement {
public:
virtual ~GraphicElement() = default;
virtual void render() const = 0;
virtual void scale(double factor) = 0;
};
class Circle : public GraphicElement {
public:
explicit Circle(double radius) : radius_(radius) {}
void render() const override {
std::cout << "Circle (radius: " << radius_ << ")\n";
}
void scale(double factor) override {
radius_ *= factor;
}
private:
double radius_;
};
class Rectangle : public GraphicElement {
public:
Rectangle(double w, double h) : width_(w), height_(h) {}
void render() const override {
std::cout << "Rectangle (" << width_ << " x " << height_ << ")\n";
}
void scale(double factor) override {
width_ *= factor;
height_ *= factor;
}
private:
double width_;
double height_;
};
void draw_scene(const std::vector<std::polymorphic<GraphicElement>>& scene) {
std::cout << "--- Rendering Scene ---\n";
for (const auto& elem : scene) {
// Enforces const propagation: elem dereferences to const GraphicElement&
elem->render();
}
}
int main() {
// 1. Construct vector holding polymorphic value objects directly
std::vector<std::polymorphic<GraphicElement>> primary_scene;
primary_scene.emplace_back(std::in_place_type<Circle>, 5.0);
primary_scene.emplace_back(std::in_place_type<Rectangle>, 4.0, 6.0);
// 2. Perform deep value copy of the entire container!
// No hand-rolled clone() loops needed; each derived object is deep-cloned.
auto cloned_scene = primary_scene;
// 3. Mutate the cloned scene elements
cloned_scene[0]->scale(2.0); // Circle radius becomes 10.0
// 4. Verify value isolation between original and cloned scenes
std::cout << "Original Scene (Unmodified):\n";
draw_scene(primary_scene);
std::cout << "\nCloned Scene (Scaled 2x):\n";
draw_scene(cloned_scene);
return 0;
}
#else
int main() {
std::cout << "std::polymorphic is available in C++26 standard library implementations.\n";
return 0;
}
#endifDemonstrates explicit lifetime rules, transfers of ownership via move semantics, and defensive checking using valueless_after_move().
#include <iostream>
#include <memory>
#include <cassert>
#if defined(__cpp_lib_polymorphic) && __cpp_lib_polymorphic >= 202406L
struct Node {
virtual ~Node() = default;
virtual int evaluate() const = 0;
};
struct LiteralNode : public Node {
int value;
explicit LiteralNode(int v) : value(v) {}
int evaluate() const override { return value; }
};
int main() {
// 1. Construct using in-place constructor
std::polymorphic<Node> node_a(std::in_place_type<LiteralNode>, 42);
std::cout << "node_a evaluation: " << node_a->evaluate() << "\n";
std::cout << "node_a valueless: " << std::boolalpha << node_a.valueless_after_move() << "\n";
// 2. Perform Move Construction
std::polymorphic<Node> node_b = std::move(node_a);
// 3. Verify transferred ownership and valueless source state
std::cout << "\n--- After Move ---\n";
std::cout << "node_b evaluation: " << node_b->evaluate() << "\n";
std::cout << "node_a valueless_after_move(): " << node_a.valueless_after_move() << "\n";
std::cout << "node_b valueless_after_move(): " << node_b.valueless_after_move() << "\n";
if (node_a.valueless_after_move()) {
std::cout << "SUCCESS: Moved-from std::polymorphic correctly marked as valueless.\n";
}
return 0;
}
#else
int main() { return 0; }
#endif-
Use
std::polymorphic<T>for Dynamic Class Hierarchies with Value Semantics:- Replace standard smart pointer containers (
std::vector<std::unique_ptr<Base>>) withstd::vector<std::polymorphic<Base>>whenever the domain calls for copyable, composable value types.
- Replace standard smart pointer containers (
-
Ensure Base Class Destructors are Virtual:
std::polymorphic<T>relies on polymorphic destruction semantics. Always declare avirtual ~T() = default;virtual destructor in base classes to guarantee defined behavior during deallocation.
-
Check
valueless_after_move()Before Accessing Moved-From Objects:- Moving a
std::polymorphic<T>leaves the source object in a valueless state. Dereferencing (*or->) a valueless object invokes undefined behavior. Always verify.valueless_after_move()if re-using moved variables.
- Moving a
-
Choose
std::polymorphic<T>vs.std::indirect<T>Correctly:- Use
std::polymorphic<T>for dynamic hierarchies involving virtual inheritance and subtyping polymorphism. - Use
std::indirect<T>(its sibling C++26 type) for single non-polymorphic types, such as implementation hiding (Pimpl idiom) or recursive struct definitions (e.g., binary tree nodes).
- Use
- Related to:
- Unordered Containers — Storing heterogeneous value types in hash maps/sets without manual pointer management.
- std::flat_map And std::flat_set — Enabling contiguous value storage of polymorphic types.
- std::optional — Representing optional values for types wrapping non-nullable dynamic objects.
- Superseded by: None.
- Supersedes: Manual
virtual clone()method patterns andstd::unique_ptr<Base>wrappers for value-based dynamic hierarchies.
Back to C++26 section.