Является ли бит std:: array совместимым со старым массивом C?
Является ли базовое представление бит для std::array<T,N> v
и a T u[N]
тем же?
Другими словами, безопасно ли скопировать N*sizeof(T)
байты от одного к другому? (Либо через reinterpret_cast
, либо memcpy
.)
Edit:
Для пояснения акцент делается на том же представлении бит и reinterpret_cast
.
Например, предположим, что у меня есть эти два класса над некоторым тривиально-скопируемым типом T
для некоторого N
:
struct VecNew {
std::array<T,N> v;
};
struct VecOld {
T v[N];
};
И есть устаревшая функция
T foo(const VecOld& x);
Если представления одинаковы, то этот вызов безопасен и позволяет избежать копирования:
VecNew x;
foo(reinterpret_cast<const VecOld&>(x));
Ответы
Ответ 1
Я говорю "да" (но стандарт этого не гарантирует).
В соответствии с [array]/2:
Массив - это совокупность ([dcl.init.aggr], которая может быть list-initialized с числом элементов до N, типы которых можно конвертировать в Т.
И [dcl.init.aggr]:
Агрегат - это массив или класс (раздел [класс]) с
-
нет пользовательских, явных или унаследованных конструкторов ([class.ctor]),
-
нет частных или защищенных нестатических данных (раздел [Class.access]),
-
нет виртуальных функций ([class.virtual]) и
-
нет виртуальных, частных или защищенных базовых классов ([class.mi]).
В свете этого "инициализируется списком" возможно только в том случае, если в начале класса нет других членов и нет vtable.
Затем data()
указывается как:
constexpr T* data() noexcept;
Возвращает: указатель такой, что [data(), data() + size())
является допустимым диапазоном, а data() == addressof(front())
.
Стандарт в основном хочет сказать "он возвращает массив", но оставляет дверь открытой для других реализаций.
Единственная возможная другая реализация - это структура с отдельными элементами, и в этом случае вы можете столкнуться с проблемами сглаживания. Но, на мой взгляд, этот подход не добавляет ничего, кроме сложности. Нет ничего, что можно получить, развернув массив в структуру.
Таким образом, не имеет смысла не использовать std::array
как массив.
Но существует лазейка.
Ответ 2
Это напрямую не отвечает на ваш вопрос, но вы просто должны использовать std::copy
:
T c[N];
std::array<T, N> cpp;
// from C to C++
std::copy(std::begin(c), std::end(c), std::begin(cpp));
// from C++ to C
std::copy(std::begin(cpp), std::end(cpp), std::begin(c));
Если T
является тривиально-скопируемым типом, это скомпилируется до memcpy
. Если это не так, то это будет выполнять поэтапное копирование и будет правильным. В любом случае, это делает правильную вещь и вполне читаемо. Нет необходимости в ручной байтовой арифметике.
Ответ 3
std::array
предоставляет метод data(), который можно использовать для копирования в/из массива c-style соответствующего размера:
const size_t size = 123;
int carray[size];
std::array<int,size> array;
memcpy( carray, array.data(), sizeof(int) * size );
memcpy( array.data(), carray, sizeof(int) * size );
Как указано в документации
Этот контейнер представляет собой совокупный тип с той же семантикой, что и структура, содержащая массив C-стиля T [N] в качестве единственного нестатического элемента данных.
поэтому кажется, что размер памяти будет совместим с массивом c-style, хотя неясно, почему вы хотите использовать "хаки" с reinterpret_cast
, когда есть правильный способ, который не имеет никаких накладных расходов.
Ответ 4
Требование к методу data()
состоит в том, что он возвращает указатель T*
такой, что:
[data(), data() + size())
- допустимый диапазон, а data() == addressof(front())
.
Это означает, что вы можете получить доступ к каждому элементу последовательно с помощью указателя data()
, и, если T
тривиально можно копировать, вы действительно можете использовать memcpy
для копирования sizeof(T) * size()
байтов в/из массива T[size()]
, так как это эквивалентно memcpy
каждому элементу индивидуально.
Однако вы не можете использовать reinterpret_cast
, поскольку это будет нарушать строгий псевдоним, поскольку data()
не требуется, чтобы на самом деле был подкреплен массивом, - а также, даже если вы должны были гарантировать, что std::array
содержит массив, поскольку С++ 17 вы не можете (даже используя reinterpret_cast
) направить указатель на массив в/из указателя на его первый член (вы должны использовать std::launder
).
Ответ 5
array
не имеет большого мандата о базовом типе, в котором вы его создаете.
Чтобы иметь возможность использовать полезные результаты при использовании memcpy
или reinterpret_cast
для копирования, тип, который вы им создали, должен быть тривиально скопирован. Сохранение этих элементов в array
не влияет на требование, чтобы memcpy
(и такое) работало только с тривиально-скопируемыми типами.
array
требуется быть непрерывным контейнером и агрегатом, что в значительной степени означает, что хранилище для элементов должно быть массивом. Стандарт показывает это как:
T elems[N]; // exposition only
Впоследствии, однако, есть примечание, что, по крайней мере, подразумевается, что он является массивом (§ [array.overview]/4):
[Примечание: переменная-член elems
показана только для изложения, чтобы подчеркнуть, что array
- это совокупность классов. Имя elems
не является частью интерфейса массивов. -end note]
[выделено курсивом]
Обратите внимание, что это действительно только конкретное имя elems
, которое не требуется.