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.

TooN 3.x

The TooN (and on github) library is now getting updates to bring it in line with C++14 (I’m skipping C++11 as an explicit target). While TooN was essentially complete and functional and hasn’t needed anything significant added in ages, I recently got a “bug” report. I say “bug” in scare quotes since it was actually a design tradeoff I made a while back to try to work around C++ not allowing non-const reference to temporaries.

The tradeoff is that some modify-in-place functions (i.e. normalize) take the argument either by reference or by copy. If the overload can’t take a reference, it copies the argument. Since that is usually caused by having a temporary, such as the result of .slice(), it simply copies the slice which still of course refers to the same data.

However, there are other reasons that a reference can’t be taken. For example, if you pass in a const Vector. Instead of failing because you’re violating constness, the library cheerfully copies the vector, normalises the copy (in place) and then destructs it. The code compiles and silently does nothing at all. Naturally I never documented that pitfall.

So, it’s time to move TooN to the modern world. That little problem is solved beautifully by rvalue references. Once the floodgates are opened, all sorts of things need doing, such as deleting the configure based homebrew/typeof/Boost version of what is now decltype.

TooN-2.0 was about half the length of TooN-1 when it reached feature parity (actually by the time it implemented all the features it had a bunch more just as an artefact of the way it was implemented). This was for a number of reasons, but mostly because it was no longer fighting against non-compliant compilers and weak optimizers. With a better language, the code was shorter and clearer. C++14 will allow me to delete a lot more code, so I fully expect 3.0 to be the shortest release of TooN yet.

To Do:

  • Simplify the code: there’s quite a few classes whose sole purpose is to compute the sometimes quite complex return types. Those can all be replaced with auto.
  • Give Matrix and Vector initialiser list constructors.
  • Replace Data and makeVector with modern variadic functions
  • Make modify-in-place functions take r-value references
  • Move assignment of Vector<Dynamic>
  • Move for Matrix<Dynamic>

Done:

  • R-value reference for normalise()
  • move constructors for Vector<Dynamic>
  • Replace hacky stuff with decltype

However, there’s still the problem of expression templates which I don’t know how to fix. That’ll be the subject of a future post.