Apparently this is legal.

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.

3 thoughts on “Apparently this is legal.

  1. offsetof is defined by its semantics in the standard, but an implementation based on source-level pointer arithmetic is not mandatory. Nor, for that matter, does an implementation that actually uses pointer arithmetic guarantee that the compiler won’t treat only that instance as special, and lead to undefined behavior for other invalid pointer arithmetic. Generally speaking, pointer arithmetic on anything other than a specific pointer whose result you know to also be either valid or one-past-the-end of an array is undefined behavior. Particularly, the definition of offsetof in terms of a NULL pointer very explicitly invokes undefined behavior if user-provided code does the same thing.

    I expect your trick is fine for the other bits of the standard you cite, but offsetof should be left out of the argument.

    Liked by 1 person

    • I meant something a little different about offsetof. I meant it can be used to move around within a struct as if it’s an array, i.e. going to pointer to the struct then doing arithmetic on that.

      Like

  2. Pingback: 1 – Apparently this is legal: float* b_ptr = &f.a and 1;

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s