C++26 introduces `std::indirect<T>` in `<memory>` (via P3019R14), a vocabulary type that gives heap-allocated objects full value semantics. Unlike `std::unique_ptr`, it provides deep copies, correct const propagation (operator->() const returns const T*), value-based comparison and hashing, and auto-generated special member functions. This eliminates the Rule-of-Five boilerplate for PIMPL idioms, enables recursive types, and makes large heap-allocated members behave like ordinary value members. The post also notes a valueless-after-move state accessible via `valueless_after_move()`, and recommends `std::optional<std::indirect<T>>` for nullable indirection.
Table of contents
The problem with unique_ptrstd::indirect — value semantics for heap-allocated objectsConclusionConnect deeperQuestions this post answers
What is std::indirect in C++26 and how does it differ from unique_ptr?
`std::indirect<T>` is a C++26 vocabulary type (from P3019R14) that owns a heap-allocated T but behaves like a value. Unlike `unique_ptr`, it provides deep copies on copy construction, correct const propagation (operator->() const returns const T*, not T*), value-based equality and three-way comparison, and auto-generated special member functions — no Rule-of-Five boilerplate required. C++ developers adopting value-type design patterns track std::indirect and similar C++26 additions on daily.dev.
How does std::indirect fix the const propagation problem with unique_ptr in C++?
`unique_ptr::operator*() const` returns a non-const T&, so a const object can still mutate its heap-allocated member — a silent correctness bug. `std::indirect::operator->() const` returns a `const T*` instead, making mutation through a const access path a compile error. The bug is structurally impossible with `std::indirect`. Developers writing const-correct C++ class hierarchies find breaking-change details like this faster on daily.dev.
What is the valueless state in std::indirect and how do I handle nullable indirection?
`std::indirect` has no null or empty state by design — it always owns an object except after being moved from. After a move, `valueless_after_move()` returns true and accessing the object is undefined behaviour. For nullable indirection, the recommended approach is `std::optional<std::indirect<T>>`. Teams upgrading codebases to C++26 and auditing move-safety edge cases like this keep up on daily.dev.