Document: P3737R3
Author: Jan Schultke
Date: 2026-02-22
Audience: LEWG
We all teach that std::array is just a wrapper for a C-style array. But the standard doesn't actually say that. The spec is permissive enough that a compliant implementation could add extra members, break trivial copyability, or inflate the size. This paper makes the simplified explanation true by standardizing what every implementation already does: a single public data member of type T[N] for nonzero N.
The zero-length case is where it gets interesting. MSVC STL's std::array<T, 0> actually holds a T and calls its constructors - a known bug that's an ABI break to fix. The paper standardizes the libstdc++/libc++ behavior: trivially copyable, non-empty, with a placeholder member. Also deletes the never-implemented "unique value" iterator requirement for empty arrays.
I've been using
std::bit_cast<std::array<std::byte, sizeof(T)>>(x)for years assuming it works becausestd::arrayhas the same layout asT[N]. Turns out that was technically relying on non-standard behavior. This paper makes it legal. Should have been done ten years ago.Welcome to C++ standardization, where the things everyone assumes are true have been technically UB for a decade and nobody noticed because every implementation did the obvious thing.
The MSVC STL section is brutal.
And fixing it is an ABI break. STL#5583 has been open since 2025. The paper basically says "when MSVC eventually breaks ABI, here's what to change to."
ABI stability: protecting the bugs of yesterday at the expense of the correctness of tomorrow.
Making
std::array<T, 0>unconditionally trivially copyable is the right call. Both libc++ and libstdc++ already do this. The alternative - making it conditional on T - adds complexity with no benefit. An empty container with no elements shouldn't care whether T has a non-trivial copy constructor.Counter-argument: it's surprising that
std::is_trivially_copyable_v<std::array<std::string, 0>>would be true whenstd::is_trivially_copyable_v<std::string>is false. Generic code that branches on trivial copyability might get confused.What would the generic code do differently?
memcpyon a zero-element container copies zero elements regardless. Trivial copyability of an empty container is a degenerate case that can't produce wrong results.The "malice_and_evil" example in Section 2.2 is perfect. A technically compliant
std::arraywithalignas(1024)and a non-trivially-copyable extra member. Nobody would ship this. But the standard allows it. The paper's thesis in one struct:The deletion of the "unique value" iterator requirement for zero-length arrays (Section 4.4) is long overdue. The requirement was never implemented by any compiler. All three major standard libraries use
T*iterators, makingbegin() == end()trivially true. The spec said something nobody did, and now the spec will stop saying it.Making the structural type property follow naturally from the data member specification is elegant. The current standard has a special paragraph for it. After this paper, it's just a consequence of the type having a single public member of type
T[N]. Less spec text, same guarantee.TIL that
std::array<int, 0> {{}}needs a non-static data member to work (otherwise double-brace initialization fails). This is why the zero-length specialization can't be an empty class - it needs something to initialize. The things you learn reading wording papers.LWG2157 has been open since 2012. Fourteen years. This paper finally absorbs it. Some bugs are patient.
The design strategy is just "standardize the greatest common denominator of libc++ and libstdc++." No new behavior. No controversial choices. Just write down what everyone already does. More papers should be this boring and this useful.
The
std::to_arrayreturn specification uses{{ a[0], ..., a[N - 1] }}- double braces. Ifstd::arrayweren't a wrapper for a C array, that specification would be nonsensical. The paper points out the standard was already implicitly assuming this layout.Section 3 mentions implicit object creation. Having the guarantee that
std::arraycontains an actual array means[intro.object]rules for providing storage and implicit lifetime apply. Without this paper, it's unclear whetherstd::array<std::byte, N>can serve as storage for implicit-lifetime types. With it, the answer is yes.The "gobbler" example - a compliant
std::arrayimplementation that swallows list-initialization into a separate member - is the kind of adversarial spec reading that makes you question everything you thought you knew about the standard library.The new
data()specification just says "Returns:arr." Two words. Replacing a paragraph about valid ranges andaddressof(front()). The simplification is beautiful.