EDIT: I posted this on /r/cpp and there’s lots of good discussion there. I’m now less sure it’s legal, but still not sure either way.

Apparently, it’s legal to take pointers to elements in a C++ structure and do arithmetic on them to get pointers to other elements (caveats apply).

struct foo { float a, b, c; }; ... foo f; float* b_ptr = &f.a + 1;

I’m about 99% sure it’s legal, provided that the class is “standard layout” (a superset of POD—there are no restrictions on constructors or destructors). The standard doesn’t seem to contradict this view. Section 9.2.13 of N3690 (the C++14 final complete draft) specifies that

Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object.

so a, b, c must all be at increasing addresses. The standard does allow arbitrary padding (9.2.19), but the presence of padding can be detected easily enough (for example sizeof(foo) == 3*sizeof(float). Having a static assert for that would in theory limit portability, but I’ve not encountered a platform where structs of a single scalar type aren’t packed.

It also doesn’t break type punning/strict aliasing rules since a, b, c are of the same type.

It’s possible that 5.7.5 says it’s illegal. 5.7.4 says that a pointer to a non array shall be treated as a pointer to an array of length 1, and 5.7.5 says that going too far over the end of an array (more than 1 element over) is undefined.

However, offsetof is well defined and depends on allowing you to move around within a standard layout class using pointer arithmetic on the pointer to the class.

TL;DR: it’s safe.

OK, why?

Well, one of my TooN2 users asked if it was possible to have a Vector with named elements (such as .x, .y, .z) for a 3-vector, but there are many other possible variants too. If it’s legal, then you could replace float my_data[3]; with float a, b, c;

Here’s an excessively simplified version of TooN to demonstrate the principle:

//Array is one data storage class #include <array> //This is another data storage class struct RGB { float f, g, b; float* data() { return &f; } }; //This is an excessively simplified Vector class. It takes //the data storage class and provides operator[]. TooN itself has more layers //and the size as part of the type, but the principle is the same template<class Base> struct Vec: public Base { float* operator[](int i) { return Base::data()[i]; } }; //Define Vec's of various lengths Vec<std::array<float,2> > v2; Vec<std::array<float,3> > v3; Vec<std::array<float,4> > v4; //Define a Vec of length 3 with named elements Vec<RGB> vRGB;

Here’s the actual code:

https://github.com/edrosten/TooN/commit/32cb582e8e8f526980e2c7355793d23a3a629c9a

You can now do:

#include <TooN/TooN.h> #include <TooN/named_elements.h> #include <TooN/named_elements.h> //Now actually make some statically allocated vectors with name members. TOON_MAKE_NAMED_ELEMENT_VECTOR(CMYK, c, m, y, k); TOON_MAKE_NAMED_ELEMENT_VECTOR(RGB, r, g, b); TOON_MAKE_NAMED_ELEMENT_VECTOR(XYZ, x, y, z); int main() { CMYK<> v = TooN::makeVector(1, 2, 3, 4); RGB<> r = TooN::makeVector(1, 2, 3); std::cout << v << std::endl; std::cout << " c = " << v.c << " m = " << v.m << " y = " << v.y << " k = " << v.k << endl; cout << v * TooN::makeVector(1, 2, 3, 4) << endl; }

The code relies on a variadic macro (C++11 inherited the C99 preprocessor which has variadic macros) to generate a vector Base class with the correct named elements in. Note that CMYK, RGB and etc are proper TooN vectors, but (much like slices) they don’t use the same base as a straightforward Vector declaration.

EDIT:

The conclusion from the various discussions is that my technique might not be allowed, though I think it is not 100% clear. A modification is to change the underlying by adding a union so that an array aliases the members:

//Array is one data storage class #include <array> //This is another data storage class struct RGB { union { struct { float f, g, b; }; float my_data[3]; }; float* data() { return my_data; } };

It appears from the standard that this is definitively not forbidden, though it’s also not explicitly allowed either.