sizeof/alignof std::array not guaranteed to match C-style arrays.

225 views
Skip to first unread message

Nicolas Guillemot

unread,
May 25, 2015, 1:09:29 AM5/25/15
to unofficial-r...@googlegroups.com
I've been having a recurring annoyance with std::array recently, which could be improved by a stricter specification of std::array.

I do a lot of graphics programming, so I pretty often have to write code to send vertex data to the GPU for rendering.
A typical workflow is as follows:
  1. Generate some vertex data CPU-side. (eg: by loading a model file.)
  2. Copy the CPU-side data to a GPU-side vertex buffer for rendering.
  3. Tell the GPU what format the vertex data I just uploaded is in (so it knows how to read and render the data.)

In OpenGL, this looks something like this:

// Specification of what one vertex looks like.
struct Vertex
{
   
float Position[3];
   
float   Normal[3];
   
float TexCoord[2];
};

// Get the loaded vertex data, and the size of the data.
const Vertex* vertexData = model.GetVertices();
size_t vertexCount = model.GetVertexCount();
size_t vertexDataSizeInBytes
= vertexCount * sizeof(Vertex);

// Create a GPU vertex buffer and upload the vertex data into it.
GLuint vertexBuffer;
glGenBuffers
(1, &vertexBuffer);
glBindBuffer
(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData
(GL_ARRAY_BUFFER, vertexDataSizeInBytes, vertexData, GL_STATIC_DRAW);

// Define the format of the vertex attributes.
// Arguments: attribute index, attribute size, attribute type, attribute normalized, attribute stride, attribute initial offset (casted to void* for historical reasons).
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, Position));

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, Normal));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, TexCoord));

// Enable the vertex attributes.
glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);

// Draw the model.
glDrawArrays(GL_TRIANGLES, 0,
vertexCount);

My annoyance comes from these specific lines of code:

// Define the format of the vertex attributes.
// Arguments: attribute index, attribute size, attribute type, attribute normalized, attribute stride, attribute initial offset.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, Position));

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, Normal));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, TexCoord));

Here I had to specify 3,3,2 as the attribute sizes, which are redundantly duplicating code because those sizes were already defined when I created struct Vertex:
struct Vertex
{
   
float Position[3];
   
float   Normal[3];
   
float TexCoord[2];
};

Therefore, I'd like to write my struct Vertex like this instead:

struct Vertex
{
    std::array<
float,3> Position;
   
std::array<float,3> Normal;
   
std::array<float,2> TexCoord;
};

This would allow me to remove the "magic numbers" for the attribute sizes:

// Define the format of the vertex attributes.
// Arguments: attribute index, attribute size, attribute type, attribute normalized, attribute stride, attribute initial offset.
glVertexAttribPointer(0, Vertex().Position.size(), GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, Position));

glVertexAttribPointer(1, Vertex().Normal.size(),   GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, Normal));
glVertexAttribPointer(2, Vertex().TexCoord.size(), GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) offsetof(Vertex, TexCoord));

Unfortunately, I don't feel comfortable designing struct Vertex like this, because it seems like you can't depend on sizeof(array<T,N>) == sizeof(T[N]) and alignof(array<T,N>) == alignof(T[N]).

For now, I rely on alternate solutions like "sizeof(Position) / sizeof(*Position)", or keeping a "static const int PositionSize = 3" and using that everywhere.

When I looked around Stack Overflow, I found threads like this one (http://stackoverflow.com/questions/8262963/stdarray-alignment) where others had found implementations where that was not necessarily the case.

Thus, in conclusion, I would like to suggest that standard library implementations be held accountable for guaranteeing that sizeof/alignof a std::array match that of an identical type/size C-style array (except the special case of a 0-sized std::array, which has no C-style array equivalent.) This would make it much easier to work with data that has a specified binary format (such as GPU vertex buffer data), and make std::array a much safer refactoring tool for replacing C-style arrays.

Sean Middleditch

unread,
May 25, 2015, 4:26:33 AM5/25/15
to Nicolas Guillemot, unofficial-r...@googlegroups.com
I agree in general, and am a bit surprised this isn't already the case.

An alternative approach is to continue using the C-style arrays and
then use std::size (N4017) or a custom replacement in place of the
std::array member .size() function.
> --
> You received this message because you are subscribed to the Google Groups
> "unofficial-real-time-cxx" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to unofficial-real-ti...@googlegroups.com.
> To post to this group, send email to
> unofficial-r...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/unofficial-real-time-cxx/8eb246b6-3d65-48c4-a688-58c297f4fbf1%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Sean Middleditch
http://seanmiddleditch.com

Matt Newport

unread,
May 25, 2015, 11:41:08 AM5/25/15
to Sean Middleditch, Nicolas Guillemot, unofficial-r...@googlegroups.com
I think std::size is actually a preferable solution. I find myself doing this kind of thing a lot in D3D11: 

    ID3D11Buffer* vsConstantBuffers[] = {cameraConstantBuffer, objectConstantBuffer, lightingConstantBuffer};
    context->VSSetConstantBuffers(0, size(vsConstantBuffers), vsConstantBuffers);

In this situation using std::array would require redundantly specifying the number of elements as a template argument rather than relying on automatically deducing the array size with a plain C array.

Matt.



Ville Voutilainen

unread,
May 25, 2015, 11:47:53 AM5/25/15
to Matt Newport, Sean Middleditch, Nicolas Guillemot, unofficial-r...@googlegroups.com
On 25 May 2015 at 18:41, Matt Newport <ma...@mattnewport.com> wrote:
> I think std::size is actually a preferable solution. I find myself doing
> this kind of thing a lot in D3D11:
>
> ID3D11Buffer* vsConstantBuffers[] = {cameraConstantBuffer,
> objectConstantBuffer, lightingConstantBuffer};
> context->VSSetConstantBuffers(0, size(vsConstantBuffers),
> vsConstantBuffers);
>
> In this situation using std::array would require redundantly specifying the
> number of elements as a template argument rather than relying on
> automatically deducing the array size with a plain C array.

There's always make_array, see
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4481.html#container.array.creation

Matt Newport

unread,
May 25, 2015, 11:55:55 AM5/25/15
to Ville Voutilainen, Sean Middleditch, Nicolas Guillemot, unofficial-r...@googlegroups.com
I didn't know about make_array, thanks. That would be a good alternative in this situation.

--
You received this message because you are subscribed to the Google Groups "unofficial-real-time-cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to unofficial-real-ti...@googlegroups.com.
To post to this group, send email to unofficial-r...@googlegroups.com.

Nicolas Guillemot

unread,
May 25, 2015, 2:44:50 PM5/25/15
to unofficial-r...@googlegroups.com, ville.vo...@gmail.com, se...@middleditch.us, nlgui...@gmail.com
On Monday, May 25, 2015 at 8:55:55 AM UTC-7, Matt Newport wrote:
I didn't know about make_array, thanks. That would be a good alternative in this situation.

On Mon, May 25, 2015 at 8:47 AM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 25 May 2015 at 18:41, Matt Newport <ma...@mattnewport.com> wrote:
> I think std::size is actually a preferable solution. I find myself doing
> this kind of thing a lot in D3D11:
>
>     ID3D11Buffer* vsConstantBuffers[] = {cameraConstantBuffer,
> objectConstantBuffer, lightingConstantBuffer};
>     context->VSSetConstantBuffers(0, size(vsConstantBuffers),
> vsConstantBuffers);
>
> In this situation using std::array would require redundantly specifying the
> number of elements as a template argument rather than relying on
> automatically deducing the array size with a plain C array.

There's always make_array, see
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4481.html#container.array.creation

The non-member size is a good option for this problem, but I think having a reliable std::array is still important.

Having something that doesn't decay to a pointer and has proper value semantics is useful. For example:

struct BoundingBox1
{
    std
::array<float,3> Minimum;
    std
::array<float,3> Maximum;
};

struct BoundingBox2
{
   
float Minimum[3];
   
float Maximum[3];
};

BoundingBox1 bbox1;
bbox1
.Minimum = { 1.0f, 2.0f, 3.0f }; // nice!
bbox1
.Maximum = { 2.0f, 3.0f, 4.0f };

BoundingBox2 bbox2;
bbox2
.Minimum = { 1.0f, 2.0f, 3.0f }; // error: assigning to an array
bbox2
.Maximum = { 2.0f, 3.0f, 4.0f }; //        from an initializer list :(

There's also other interesting things with std::array, like the overloaded lexicographical comparisons (operator== operator< etc.) which would compare pointers when used on C arrays.
 

Matt Newport

unread,
May 25, 2015, 3:34:13 PM5/25/15
to Nicolas Guillemot, unofficial-r...@googlegroups.com, Ville Voutilainen, Sean Middleditch
Right, those other std::array benefits are good points. Value semantics is definitely nice for class members and the overloaded operators could come in handy too. I agree that it should be guaranteed that size and alignment match native arrays for std::array.

--
You received this message because you are subscribed to the Google Groups "unofficial-real-time-cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to unofficial-real-ti...@googlegroups.com.
To post to this group, send email to unofficial-r...@googlegroups.com.

Scott Wardle

unread,
May 25, 2015, 11:50:51 PM5/25/15
to Matt Newport, Nicolas Guillemot, unofficial-r...@googlegroups.com, Ville Voutilainen, Sean Middleditch
I don't think I would use std::array over c array I think I would like to fix c array.  I just don't like the build time over head. :) 

That being said if we have a std::array you might as well have it work right. Alignment should be same same as c array.  I can't think of a good reason otherwise. 

Scott Wardle

Paul Pedriana

unread,
May 26, 2015, 3:03:11 AM5/26/15
to Scott Wardle, Ville Voutilainen, Matt Newport, Nicolas Guillemot, unofficial-r...@googlegroups.com, Sean Middleditch

Is there a rationale for why a standard library implementation would want to unilaterally specify a different alignment for std::array?

-----------------------------------------------------------------

Jonathan Wakely

unread,
Jun 10, 2015, 6:34:25 PM6/10/15
to unofficial-r...@googlegroups.com
On Monday, 25 May 2015 06:09:29 UTC+1, Nicolas Guillemot wrote: 
Unfortunately, I don't feel comfortable designing struct Vertex like this, because it seems like you can't depend on sizeof(array<T,N>) == sizeof(T[N]) and alignof(array<T,N>) == alignof(T[N]).

static_assert(
    sizeof(array<T,N>) == sizeof(T[N]) and alignof(array<T,N>) == alignof(T[N]),
    "this implementation is sane, you can get back to work and stop worrying"
);


Reply all
Reply to author
Forward
0 new messages