C++ Programming

5 Reasons make_unique Rejects Bounded Arrays (2025 Fix)

Ever wondered why `std::make_unique<T[N]>` fails in C++? Discover the 5 key reasons behind this limitation and how the upcoming C++26 standard plans to fix it.

D

Dr. Alistair Sterling

C++ committee member and author specializing in modern C++ memory management techniques.

6 min read9 views

The `make_unique` Conundrum: Why Bounded Arrays Get the Cold Shoulder

In the world of modern C++, `std::make_unique` is the gold standard for creating dynamically allocated unique objects. It’s safe, efficient, and prevents common memory leaks by bundling allocation and smart pointer construction into a single, exception-safe operation. You can use it to create single objects and even dynamic arrays of unknown size at compile time:

// Works perfectly!
auto single_ptr = std::make_unique<int>(42);

// Also works perfectly for dynamic (unbounded) arrays!
auto array_ptr = std::make_unique<int[]>(10); // Allocates 10 ints

But then you try to allocate a C-style array with a size known at compile-time—a bounded array—and you're met with a stark compiler error:

// Why won't you compile?!
auto bounded_array_ptr = std::make_unique<int[10]>(); // ERROR!

This inconsistency has puzzled developers for years. Why does the language provide a tool that handles single objects and unbounded arrays, but explicitly rejects their bounded cousins? It feels like a strange omission. The good news is that this long-standing gap is slated to be fixed in C++26. In this article, we'll dive into the five core reasons behind this design decision and explore how the upcoming standard will finally resolve it.

Reason 1: A Clash in the C++ Type System

The primary reason lies in the fundamental distinction C++ makes between unbounded arrays (`T[]`) and bounded arrays (`T[N]`). They are not just different; they are treated as entirely separate types by the compiler.

  • `T[]` (Unbounded Array): This type represents an array of `T` with a size that is not known at compile time. It’s a type that can only exist in specific contexts, like function parameters or as the template argument for `std::unique_ptr`. It signifies a pointer to the first element of a dynamically sized block of memory.
  • `T[N]` (Bounded Array): This type represents an array of `T` with a size `N` that is a compile-time constant. This is a complete object type. You can create an instance of it on the stack, like `int my_array[10];`.

The original `std::make_unique` implementation provided two overloads: one for non-array types (`T`) and one for unbounded arrays (`T[]`). Adding a third, distinct overload for `T[N]` was seen as adding complexity for a case that had a viable, and arguably better, alternative.

Reason 2: Ambiguous Function Overloads

Function template overloading is a powerful but delicate mechanism. The C++ standard committee must be extremely careful not to introduce ambiguities. The existing overloads for `std::make_unique` are:

// 1. For non-array types
template<class T, class... Args>
unique_ptr<T> make_unique(Args&&... args);

// 2. For unbounded arrays
template<class T>
unique_ptr<T> make_unique(size_t size); // where T is U[]

A hypothetical `make_unique<T[N]>()` would need its own template specialization. The problem is that bounded arrays often decay into pointers (`T*`), which could create confusion with the primary template for `make_unique`. While solvable with advanced template metaprogramming (like SFINAE or concepts), the committee initially decided the added complexity wasn't justified, especially given the existence of `std::array`.

Reason 3: The `delete` vs. `delete[]` Mandate

The entire purpose of `std::unique_ptr`'s array specializations is to ensure the correct deallocation function is called. A `std::unique_ptr<T>` calls `delete`, while a `std::unique_ptr<T[]>` calls `delete[]`. Mixing these up leads to undefined behavior.

A smart pointer managing a bounded array, `int[10]`, must also use `delete[]`. Therefore, `std::make_unique<int[10]>()` would need to return a `std::unique_ptr<int[]>`. This mapping—where the requested type `T[N]` doesn't match the smart pointer's managed type `T[]`—was considered unintuitive. It breaks the mental model where `make_unique` gives you a `unique_ptr`. Forcing this slightly awkward mapping in the standard library was a point of contention.

Reason 4: The Rise of a Better Alternative: `std::array`

Perhaps the most compelling reason for the omission was the introduction of `std::array` in C++11. If you need a fixed-size array on the heap, the modern, idiomatic C++ way is not to use a raw C-style array, but to wrap it in a `std::array`:

// The modern, explicit, and safe way
auto ptr = std::make_unique<std::array<int, 10>>();

This approach has several advantages:

  • Explicitness: The code clearly communicates its intent.
  • Rich Interface: You get all the benefits of `std::array`, like `.size()`, `.begin()`, `.end()`, and bounds-checking with `.at()`.
  • Type Safety: It returns a `unique_ptr<std::array<int, 10>>`. The type is preserved, and the pointer correctly uses `delete` (not `delete[]`) because it's deleting a single `std::array` object.

From the committee's perspective, providing a direct `make_unique` would encourage the use of C-style arrays over a superior standard library container. They chose to guide developers toward the better tool.

Reason 5: Avoiding the Pointer Decay Trap

C-style arrays have a notorious habit of "decaying" into pointers at the slightest provocation. For instance, when you pass an array to a function, what the function actually receives is a pointer to the first element. The size information is lost.

void process_data(int data[]) { // Actually receives int*
  // sizeof(data) here gives the size of a pointer, not the array!
}

int main() {
  int my_data[20];
  process_data(my_data); // Size information is lost here
}

This behavior can lead to subtle bugs. By disallowing `make_unique`, the standard encourages patterns that preserve size information, like using `std::array` or being explicit with `make_unique(N)`. It sidesteps a category of bugs associated with array-to-pointer decay by simply not participating in that part of the C-style ecosystem.

Dynamic Array Allocation Methods Compared

Comparison of Heap-Allocated Array Techniques
Method Syntax Size Knowledge Deleter Used Best For
`new T[N]` (Raw) `int* p = new int[10];` Runtime `delete[]` (manual) Legacy code; avoid in modern C++.
`make_unique<T[]>(N)` `auto p = make_unique<int[]>(10);` Runtime `delete[]` (automatic) Dynamically-sized arrays where size is known at runtime.
`make_unique<std::array>` `auto p = make_unique<std::array<int, 10>>();` Compile-time `delete` (automatic) Fixed-size arrays with a rich, modern container interface.
`make_unique<T[N]>()` (C++26) `auto p = make_unique<int[10]>();` Compile-time `delete[]` (automatic) Convenience for fixed-size arrays when `std::array` overhead is not desired.

The C++26 Fix: Welcoming `make_unique` into the Fold

Despite the valid reasons for its original exclusion, the C++ community has consistently requested support for `make_unique`. The argument is one of consistency and convenience. As developers have become more adept with modern C++, the initial concerns about confusion have lessened. Responding to this, the C++ committee is set to adopt proposal P1020 (or a successor) for the C++26 standard, finally adding this long-awaited feature.

How the Fix Works: A New Overload

The fix involves adding a new constrained overload to the `` header. It will look something like this:

// 3. New overload in C++26 for bounded arrays
template<class T> requires (std::is_bounded_array_v<T>)
unique_ptr<T> make_unique();

This template is constrained to only activate when `T` is a bounded array type (e.g., `int[10]`). It will allocate memory for `N` elements and return a `std::unique_ptr<T>` which, in this context, correctly resolves to a type that uses `delete[]`. This elegant solution uses modern C++ features (like concepts with `requires`) to eliminate the ambiguity that worried the original designers.

Code in Action: Before and After C++26

Let's see the practical impact. Creating a heap-allocated, fixed-size array of 50 characters becomes much more direct.

Before C++26 (the `std::array` way):

// Verbose but effective
auto buffer = std::make_unique<std::array<char, 50>>();
(*buffer)[0] = 'a';

With C++26:

// Direct, concise, and clear
auto buffer = std::make_unique<char[50]>();
buffer[0] = 'a';

The new syntax is undeniably more concise and feels more natural for those coming from a C-style array background, while still providing the full safety of `std::unique_ptr`.

A More Consistent and Safer Future for C++

The initial decision to exclude `make_unique` was a pragmatic one, rooted in a desire to avoid ambiguity, promote the superior `std::array` container, and prevent common C-style array pitfalls. It was a choice that prioritized safety and idiomatic design over syntactical completeness.

However, as the language and its user base have matured, the calculus has changed. The upcoming C++26 fix recognizes that convenience and consistency have value. By leveraging modern template techniques, the standard can now provide this feature without compromising safety. This change closes a small but noticeable gap in the language's memory management toolkit, making C++ an even more powerful and consistent language for developers.