Является ли `std:: array <T, 0>` default конструктивным, где `T` не является конструктивным по умолчанию?

Рассмотрим приведенный ниже код:

#include <array>

struct T
{
    T() = delete;
};

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

Мы по умолчанию инициализируем массив размером 0. Поскольку нет элементов, конструктор T не должен быть вызван.

Однако Clang по-прежнему требует конструкцию T, а GCC принимает указанный выше код.

Обратите внимание, что если мы изменим инициализацию массива на:

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

Clang принимает его на этот раз.

Предоставляет ли конструктор T, не поддерживаемый по умолчанию, предотвращать конструкцию std::array<T, 0> по умолчанию?

Ответы

Ответ 1

Благодаря @TC, как указано в comment, он адресован в LWG 2157, который по-прежнему остается открытым вопросом на момент написания этой статьи.

В предлагаемой резолюции добавляется этот пункт (выделено мной):

Неопределенная внутренняя структура массива для этого случая допускает инициализацию типа:

array<T, 0> a = { };

и указанные инициализации должны быть действительными , даже если T не является конструктивным по умолчанию.

Итак, ясно, что предполагаемое поведение должно иметь конструкцию по умолчанию std::array<T, 0> по умолчанию, даже если T не является.

Ответ 2

Поскольку нет элементов, конструктор T не следует вызывать. Предоставляет ли конструктор T предотвращающий конструкцию T std::array<T, 0> конструкцию по умолчанию?

В стандарте не указывается, какой макет std::array<T, 0> должен иметь для нас ответ. Говорят, что специализация массива нулевого размера ведет себя следующим образом:

[array.zero]

1 массив должен обеспечивать поддержку для специального случая N == 0.
2 В случае, когда N == 0, begin() == end() == уникальное значение. Возвращаемое значение данных() не указано.
3 Эффект вызова front() или back() для массива нулевого размера - undefined.
4 Функция функции swap() должна иметь спецификацию исключения без метаданных.

Поведение, которое вы заметили, скорее всего связано с различиями в реализации.

Ответ 3

Этот вопрос объясняет, что происходит с clang и std::array Удаленный конструктор по умолчанию. Объекты все еще могут быть созданы... иногда

Но с gcc разница исходит из кода библиотеки. В gcc-базе данных gcc действительно есть конкретная деталь реализации, которая имеет отношение к этому вопросу, поскольку @StoryTeller упомянул

gcc имеет специальный случай для std::array с размером 0, см. следующий код из заголовка <array> (из 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; }
};

как вы можете видеть, существует специализация __array_traits (которая используется в std::array для базового массива), когда размер массива равен 0, у которого даже нет массива типа, на котором он был установлен, Тип _Type не является массивом, а пустой структурой!

Вот почему нет вызываемых конструкторов.