There is sometimes a situation in which one needs to get the relative offset of a structure field, common examples of this include serialization frameworks which aid to serialize objects, vertex attributes for rendering (D3D, GL.), etc.
The most common technique for getting this information is through the offsetof
macro defined in stddef.h
. Unfortunately using the macro in C++ comes with a
new set of restrictions that prevent some (subjectively valid) uses of it.
Intuition would have you believe that using offsetof
on classes/structures with
standard-layout would work. However when a base class has data members, the
deriving class is not considered standard-layout. Presumably the reason for
this is that C++ wants to beable to permit very unusual layouts where a given
inherited class might not have the same offset in all instances?
A less relaxed compiler will emit diagnostic for this as a result:
struct vec2 { float x, y; };
struct vec3 : vec2 { float z; };
struct vec4 : vec3 { float w; };
y_offset = offsetof(vec4, y);
Even though vec4 definitely has the following memory layout:
0 4 8 C
| x | y | z | w |
This becomes a problem and often leads to techniques that lead to illegal code such as the following technique present in many game engines:
vec4 *v = 0;
y_offset = &v->y;
Various people would have you believe it's not undefined since it tends to be
the common technique implored to implement the offsetof
macro. Modern compilers
do provide intrinsics however; (GCC, Clang) now use __builtin_offsetof
because
they're now beginning to optimize based on the assumption code does not depend
on undefined behavior. For more information on why you should no longer write
code like that, check out these in depth analysis of the technique:
http://www.viva64.com/en/b/0306/
This leaves us in a tough position however. How can we work around this limitation without invoking undefined behavior?
One technique would be to create an object on stack and calculate the offset like so:
vec4 v;
v_offset = size_t(&v.y) - size_t(&v);
However this is not a valid analog for offsetof
which is usuable in constant
expressions as it yields a constant value. Which means code like the following:
enum { memory_needed = offsetof(vec4, y) };
unsigned char memory_for_x_and_y[memory_needed];
Cannot be realized with this technique.
One option would be to maintain a set of structures with the same layout without
any inheritance but that leads to a maintenance hell. So instead I decided to
explore C++11s constexpr
to see if there was a way to accomplish this.
Unfortunately C++11s constexpr
rules are too restrictive to allow us to have a
local object in the function, but C++14 did relax those rules allowing us to
achieve what we want:
template <typename T1, typename T2>
inline size_t constexpr offset_of(T1 T2::*member) {
constexpr T2 object {};
return size_t(&(object.*member)) - size_t(&object);
}
As long as T2
has a default constructor which is constexpr
this works. The
implicit default constructors for our vec classes are implicitly constexpr
so
this works:
y_offset = offset_of(&vec4::y);
However C++14 is fairly new and I needed a C++11 compatible option which yields constant values. After a little bit of work, I came up with the following solution:
template <typename T1, typename T2>
struct offset_of_impl {
static T2 object;
static constexpr size_t offset(T1 T2::*member) {
return size_t(&(offset_of_impl<T1, T2>::object.*member)) -
size_t(&offset_of_impl<T1, T2>::object);
}
};
template <typename T1, typename T2>
T2 offset_of_impl<T1, T2>::object;
template <typename T1, typename T2>
inline constexpr size_t offset_of(T1 T2::*member) {
return offset_of_impl<T1, T2>::offset(member);
}
Not as pretty but gets the job done. The compiler diagnostics for misusing this tend to be legible, if you're feeling
a bit zealous you could use enable_if
to check if the type has a default constructor before stamping out the function. But I found that the diagnostics for that were less legible than not having any check at all.
Greetings friends :),
First some bad news:
reinterpret_cast
is not legal inconstexpr
expressions. To be clear, a cast such assize_t(...)
is areinterpret_cast
. @graphitemaster's code above compiles due to a known bug in gcc, see: https://stackoverflow.com/a/24400015/2013747 It does not compile in clang 5.0.0.And a minor point:
operator&
could be overloaded, sostd::addressof
should be used.Below is my (rather hairy) attempt at a solution. To avoid the "class sample," we can use a
union
. In C++11 unions can contain members with non-trivial ctors, and you can specify that the default constructs an alternative, trivial, member. (Then the question remains: is it legal to do address computations on the non-active members of a union?). This version also avoids use ofreinterpret_cast
. [EDIT: but unfortunately, it is not actually constexpr either, at least not in C++11. gcc does compile it in C++17 mode. clang does not]Here's a code sandbox to play with: https://wandbox.org/permlink/SVcjVI7k6ezgcUgs
One problem is that
std::addressof
may not have defined behavior when its parameter is not the active union member, or perhaps those expressions that form the parameters ofstd::addressof
may themselves be undefined. We need a language lawyer to resolve this. (See discussion here: https://stackoverflow.com/questions/49775980/is-it-well-defined-to-use-stdaddressof-on-non-active-union-members )A final note: in C++17,
offsetof
is conditionally supported, which means that you can use it on any type (not just standard layout types) and the compiler will error if it can't compile it correctly. That appears to be the best option if you can live with C++17 and don't needconstexpr
support.