Does the standard require that std::array<T, 0> behave like it does not have trailing padding?

222 Views Asked by At

std::array is required by the standard to be well-defined even when the size is zero, so that std::array<T, 0> is well-defined even though int[0] is not allowed (though GCC implements an extension for it).

However, on GCC, Clang, and MSVC, std::array<T, 0> is not treated as if it is a zero-sized struct, i.e. it does not act like it has trailing padding, and so [[no_unique_address]] and the empty base optimization does not work with it. For example:

#include <array>

struct Tester {
    [[no_unique_address]] std::array<char, 0> arr;
    char c;
};

struct Tester2 : std::array<char, 0> {
    char c;
};

// true on GCC, Clang, and MSVC at least:
static_assert(sizeof(Tester) == 2);
static_assert(sizeof(Tester2) == 2);

This is surprising, since it's obviously implementable as an empty struct. Is this something mandated by the standard?


Note: I've looked into the code for libstdc++, and it works like this because std::array<char, 0> contains an instance of an empty struct that is not [[no_unique_address]] and not using empty base class optimization, i.e. it looks something like this:

template <typename T>
struct array<T, 0> {
    struct {} empty; // no [[no_unique_address]]!
};

But this doesn't doesn't explain why it was designed this way. Even though [[no_unique_address]] wasn't a thing until C++20, the empty base class optimization is used liberally in other parts of the standard library, so it's definitely something that could be done if desired.

Edit: My question got marked as a duplicate of Are C++ compilers actually compliant with zero-size array SFINAE rule?. This is not true at all; my question is about the library feature std::array, which is well-defined by the standard even when the size is 0. The linked question is about the language feature of arrays.

1

There are 1 best solutions below

0
On

Could it be that the empty struct is related to the double-brace initialisation "issue" in C++11?

std::array<T,N> myArray = {{ ... }};

Before N3526 there were scenarios where braces needed to be doubled to list initialise a std::array. If std::array<T,0> had been implemented as a simple empty struct, initialisation via double-braces wouldn't have compiled.

So maybe to ensure that all std::array instantiations could be initialised with the same syntax they added an empty inner class?