15

Consider the code below:

#include <array>

struct T
{
    T() = delete;
};

int main()
{
    std::array<T, 0> a;
    a.size();
}

We default initialize a 0-sized array. Since there's no elements, no constructor of T should be called.

However, Clang still requires T to be default constructible, while GCC accepts the code above.

Note that if we change the array initialization to:

std::array<T, 0> a{};

Clang accepts it this time.

Does non-default-constructible T prevent std::array<T, 0> from being default-constructible?

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
Jamboree
  • 5,139
  • 2
  • 16
  • 36
  • 2
    Aggregate initializing is valid even if there are deleted default constructors. But default initializing (iirc) is not. I think clangs behavior is reasonable. Executing things or not can't have influence on whether or not code is valid at compile time. – Johannes Schaub - litb Jun 28 '17 at 10:32
  • 1
    @JohannesSchaub-litb Do you expect an element to be initialized for a 0-sized array? Clang does [that](https://wandbox.org/permlink/LashAGkeVwnfsodC), which looks insane to me. If there's no elements, no elements should be initialized. – Jamboree Jun 28 '17 at 13:07
  • 2
    See [LWG 2157](https://timsong-cpp.github.io/lwg-issues/2157). – T.C. Jun 28 '17 at 22:23

3 Answers3

5

Since there's no elements, no constructor of T should be called.
Does non-default-constructible T prevent std::array<T, 0> from being default-constructible?

The standard doesn't specify what layout std::array<T, 0> should have for us to answer that. The zero sized array specialization is only said to behave as follows:

[array.zero]

1 array shall provide support for the special case N == 0.
2 In the case that N == 0, begin() == end() == unique value. The return value of data() is unspecified.
3 The effect of calling front() or back() for a zero-sized array is undefined.
4 Member function swap() shall have a non-throwing exception specification.

The behavior you note is most probably due to differences in implementation alone.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • It's not only about layout. Clang's implementation actually initializes one element for 0-sized array at runtime. – Jamboree Jun 28 '17 at 13:18
  • 1
    @Jamboree - Because they aren't bound to any specific layout. Their prerogative under the standard. – StoryTeller - Unslander Monica Jun 28 '17 at 13:34
  • 1
    If the argument is "this list doesn't forbid creating an element, so creating an element is allowed", do you agree with an implementation that calls `std::exit()` for `array a;`? I do not yet understand the argument, I think. – Johannes Schaub - litb Jun 28 '17 at 19:04
  • @JohannesSchaub-litb - No I do not agree with such an implementation. This tenancy to take UB and use it in a reductio ad absurdum argument is quite tedious. The standard was under-specified, so Clang had leeway. LWG 2157 addresses this to prevent this leeway, like Jamboree pointed out in the answer. – StoryTeller - Unslander Monica Jun 29 '17 at 05:31
5

Thanks to @T.C., as pointed out in his comment, it's addressed in LWG 2157, which is still an open issue as of this writing.

The proposed resolution adds this bullet point (emphasis mine):

The unspecified internal structure of array for this case shall allow initializations like:

array<T, 0> a = { };

and said initializations must be valid even when T is not default-constructible.

So it's clear that the intended behavior is to have std::array<T, 0> default constructible even when T is not.

Jamboree
  • 5,139
  • 2
  • 16
  • 36
  • It's clear that that's the behaviour intended by the proposer (and by JW) now, but not necessarily by anyone else and not necessarily at the time that compiler versions in question were written/released! Indeed, the text you link to suggests that the original intention was literally the opposite. This is still the best answer though. – Lightness Races in Orbit Jun 29 '17 at 13:58
2

This question explains what happens with clang and std::array Deleted default constructor. Objects can still be created... sometimes

But with gcc the difference comes from the library code. There is indeed a specific implementation detail in the gcc codebase that is relevant to this question as @StoryTeller mentioned

gcc has a special case for std::array with a size of 0, see the following code from their <array> header (from gcc 5.4.0)

template<typename _Tp, std::size_t _Nm>
struct __array_traits
{
  typedef _Tp _Type[_Nm];

  static constexpr _Tp&
  _S_ref(const _Type& __t, std::size_t __n) noexcept
  { return const_cast<_Tp&>(__t[__n]); }

  static constexpr _Tp*
  _S_ptr(const _Type& __t) noexcept
  { return const_cast<_Tp*>(__t); }
};

template<typename _Tp>
struct __array_traits<_Tp, 0>
{
 struct _Type { };

 static constexpr _Tp&
 _S_ref(const _Type&, std::size_t) noexcept
 { return *static_cast<_Tp*>(nullptr); }

 static constexpr _Tp*
 _S_ptr(const _Type&) noexcept
 { return nullptr; }
};

as you can see, there is a specialization of __array_traits (which is used in std::array for the underlying array) when the array size is 0, that doesn't even have an array of the type it's templated on. The type _Type is not an array, but an empty struct!

That is why there are no constructors invoked.

Curious
  • 20,870
  • 8
  • 61
  • 146