Is the underlying bit representation for an std::array
and a T u[N]
the same?
In other words, is it safe to copy
array
doesn't mandate much about the underlying type over which you instantiate it.
To have any possibility of useful results from using memcpy
or reinterpret_cast
to do a copy, the type you instantiated it over would have to be trivially copyable. Storing those items in an array
doesn't affect the requirement that memcpy
(and such) only work with trivially copyable types.
array
is required to be a contiguous container and an aggregate, which pretty much means that the storage for the elements must be an array. The standard shows it as:
T elems[N]; // exposition only
It later, however, has a note that at least implies that it's being an array is required (§[array.overview]/4):
[Note: The member variable
elems
is shown for exposition only, to emphasize thatarray
is a class aggregate. The nameelems
is not part of array’s interface. —end note]
[emphasis added]
Note how it's really only the specific name elems
that isn't required.
The requirement on the data()
method is that it return a pointer T*
such that:
[data(), data() + size())
is a valid range, anddata() == addressof(front())
.
This implies that you can access each element sequentially via the data()
pointer, and so if T
is trivially copyable you can indeed use memcpy
to copy sizeof(T) * size()
bytes to/from an array T[size()]
, since this is equivalent to memcpy
ing each element individually.
However, you cannot use reinterpret_cast
, since that would violate strict aliasing, as data()
is not required to actually be backed by an array - and also, even if you were to guarantee that std::array
contains an array, since C++17 you cannot (even using reinterpret_cast
) cast a pointer to an array to/from a pointer to its first member (you have to use std::launder
).
std::array
provides method data() which can be used to copy to/from c-style array of proper size:
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 );
As stated on documentation
This container is an aggregate type with the same semantics as a struct holding a C-style array T[N] as its only non-static data member.
so it seems that memory footprint would be compatible with c-style array, though it is not clear why you want to use "hacks" with reinterpret_cast
when there is a proper way which does not have any overhead.
I say yes (but the standard does not guarantee it).
According to [array]/2:
An array is an aggregate ([dcl.init.aggr]) that can be list-initialized with up to N elements whose types are convertible to T.
And [dcl.init.aggr]:
An aggregate is an array or a class (Clause [class]) with
no user-provided, explicit, or inherited constructors ([class.ctor]),
no private or protected non-static data members (Clause [class.access]),
no virtual functions ([class.virtual]), and
no virtual, private, or protected base classes ([class.mi]).
In light of this, "can be list-initialized" is only possible if there are no other members in the beginning of the class and no vtable.
Then, data()
is specified as:
constexpr T* data() noexcept;
Returns: A pointer such that[data(), data() + size())
is a valid range, anddata() == addressof(front())
.
The standard basically wants to say "it returns an array" but leaves the door open for other implementations.
The only possible other implementation is a structure with individual elements, in which case you can run into aliasing problems. But in my view this approach does not add anything but complexity. There is nothing to gain by unrolling an array into a struct.
So it makes no sense not to implement std::array
as an array.
But a loophole does exist.
This doesn't directly answer your question, but you should simply use 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));
If T
is a trivially copyable type, this'll compile down to memcpy
. If it's not, then this'll do element-wise copy assignment and be correct. Either way, this does the Right Thing and is quite readable. No manual byte arithmetic necessary.