3

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.

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • Just oversight? And @BoP is right, you're talking about particular implementations but you're asking about the standard. – Paul Sanders Aug 07 '22 at 08:54
  • 3
    The standard has practically no reqiurements for a zero size std::array, except that `size() == 0` and `begin() == end()`. http://eel.is/c++draft/array.zero – BoP Aug 07 '22 at 08:57
  • The requirement of the standard of types in the standard library is that they provide the required interface (for code that uses it) and required behaviour of operations. It doesn't actually mandate use of attributes like `[[no_unique_address]]` as part of the implementation of a type. As to why particular implementations do what they do, that's partly historical (e.g. `std::array` existed before C++20, so is implementable without C++20 attributes) and a possible maintenance concern (even if the library implementers intend to use C++20 attributes, they may not have gotten around to it). – Peter Aug 07 '22 at 16:11
  • That seems reasonable, but it's fairly surprising that all three implementations chose to specialize `array` in this way when they could have simply left out the empty struct inside the `array`. – Bernard Aug 07 '22 at 16:19

1 Answers1

0

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?

Parker Coates
  • 8,520
  • 3
  • 31
  • 37