41

Is the underlying bit representation for an std::array<T,N> v and a T u[N] the same?

In other words, is it safe to copy N*sizeof(T) bytes from one to the other? (Either through reinterpret_cast or memcpy.)

Edit:

For clarification, the emphasis is on same bit representation and reinterpret_cast.

For example, let's suppose I have these two classes over some trivially copyable type T, for some N:

struct VecNew {
    std::array<T,N> v;
};

struct VecOld {
    T v[N];
};

And there is the legacy function

T foo(const VecOld& x);

If the representations are the same, then this call is safe and avoids copying:

VecNew x;
foo(reinterpret_cast<const VecOld&>(x));
6
  • Are you doing the copy using data/&array_name[0] or using the name of the "array" itself? Commented Sep 7, 2016 at 18:28
  • 5
    Not through reinterpret_cast, because of strict aliasing. Commented Sep 7, 2016 at 18:28
  • 1
    Hmm... the original question was about copying, the new question is about reinterpret_cast-ing. That's somewhat different... Commented Sep 7, 2016 at 21:06
  • 1
    It looks like you're trying to modernize legacy C++ code by replacing old constructs by new ones, right? Commented Sep 8, 2016 at 10:39
  • 1
    Then somebody changes VecNew by adding new field for example and enjoy debugging. No, thanks. Commented Sep 8, 2016 at 13:13

5 Answers 5

20

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.

Sign up to request clarification or add additional context in comments.

3 Comments

nitpick: std::copy doesn't always compile down to memcpy it's an implementation detail. For example VC++ uses memmove for byte copies.
I'm torn. This is a great answer... for a different question!
godbolt.org/g/SGdWwp Looks like it is doing the fast copy only if the two arguments are of the same array type(only test and test3 compile to memmove).
16

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.

2 Comments

It is exactly the "so it seems" part that I'd like to get clarified.
you did not answer why
11

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, and data() == 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.

5 Comments

I disagree that aliasing problems could occur. What is your reasoning for that claim?
A structure and an array are incompatible types in terms of strict aliasing.
I don't think your interpretation of the strict aliasing rule is correct. If that were the case, then an array type would also be incompatible with its element type, which is clearly absurd.
His assertion on strict aliasing does not imply what you claim it does.
@Brian That's not what RustyX was saying. An array has never been compatible with a struct having the same number of same-typed members. However, even your tangential inference about the compatibility of pointers to arrays versus pointers to their elements is soon to be all too true! See ecatmur's answer regarding the fun in store from the in-drafting P0137R1. And please, if you are so inclined and in a position to, file a National Body comment expressing scepticism of it.
10

The requirement on the data() method is that it return a pointer T* such that:

[data(), data() + size()) is a valid range, and data() == 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 memcpying 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).

18 Comments

Re " 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)", that sounds interesting: the committee going lunatically berserk! More info please. In the meantime I'll make some popcorn.
@Cheersandhth.-Alf "a pointer to an array cannot be cast to/from a pointer to its first element": see open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html
Uhm, re the link, that's a wall of text. Several hundred kilometers of it. Can you sort of vaguely indicate a region of, say, less than 203 meters?
It seems this is about giving a compiler vendor a controlling interest in the management of the standard, whence, that compiler's shortcomings and silly behaviors become standardized. Oh well.
@underscore_d it's not about danger, it's about optimization; a lot of scientific code (cough SPEC cough) can be effectively accelerated if the compiler assumes that different-sized arrays and pointers don't alias even when the element type is the same. The speed-ups this yields are considered (by compiler authors, and, being fair, to their customers writing scientific, Fortran-style code) to be worth the potential confusion and breakage to their users writing more systems- or object-oriented code.
|
7

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 that array is a class aggregate. The name elems 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.

7 Comments

The new draft got rid of that part. Now we just have that it's an aggregate that can be list initialized with N Ts (but +1).
@Barry: I'm not at all sure that really changes much. Offhand, I don't see a way of meeting its requirements (contiguous container, aggregate) except by having only one data member, which is an array. I suppose if you could assure against padding between elements, you could create a variadic template of discrete elements, but only because the elements would still be addressable like an array.
The initialization couldn't work if array wasn't a simple struct wrapper of a raw array.
@JerryCoffin Oh I'm not saying std::array isn't definitely a wrapper around a raw array. I'm just saying that now the wording around that description is totally different (not sure what the significance of that chance is tbh. just pointing it out).
The initialization (but not other parts)could work if the storage were discrete members in the correct order.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.