I was toying around with a class where I wanted to index into it with an operator[], while also being able to access the fields.
I've attached an MCVE below of what I'm trying to do, which is be able to access a member variable through the variable itself, but also with some pointer offsets (ex: if there's an a, and b, then I can access b by the name, or access it by &a + 1 if they're the same type and located sequentially without padding).
I'm worried that I'll be running into undefined behavior and won't know it. Originally I was trying to do a "union with 1) members that are floats, and 2) array of floats" but I found out that it's undefined behavior. I tried looking up in the standard if what I'm about to do below is undefined behavior but wasn't able to find it (which obviously does not mean it doesn't exist, I easily could have missed it).
Since I'm also using CRTP to do this, I figure since I'm casting to itself that it should be okay as long as inheritance doesn't provide any members.
To make sure that this is possibly legal in C++, I added a bunch of static asserts which:
- Make sure it's a standard layout, so I can use offsetof for other static asserts static_assert(std::is_standard_layout_v<Color>);
- Make sure it's trivial static_assert(std::is_trivial_v<Color>);
- Make sure the offsets are sequential static_assert(offsetof(Color, r) == 0);,static_assert(offsetof(Color, g) == sizeof(float));,static_assert(offsetof(Color, b) == 2 * sizeof(float));
- Make sure nothing was added to the class from inheriting static_assert(sizeof(Color) == 3 * sizeof(float));
The code:
#include <iostream>
using namespace std;
template <typename T>
class ColorCRTP {
    T& getInstance() {
        return *static_cast<T*>(this);
    }
public:
    // Is it UB to do this when we set values from the
    // fields themselves in the actual class?
    float& operator[](size_t index) {
        // Assume the inheriting class *always* is only a
        // series of sequential members of the exact same
        // type.
        return *(&getInstance().r + index);
    }
};
struct Color : ColorCRTP<Color> {
    float r;
    float g;
    float b;
    Color() = default;
    Color(float r, float g, float b) : r(r), g(g), b(b) { }
};
// Do these help guarantee that I am not performing UB?
static_assert(std::is_standard_layout_v<Color>);
static_assert(std::is_trivial_v<Color>);
static_assert(offsetof(Color, r) == 0);
static_assert(offsetof(Color, g) == sizeof(float));
static_assert(offsetof(Color, b) == 2 * sizeof(float));
static_assert(sizeof(Color) == 3 * sizeof(float));
int main() {
    Color c{0.5f, 0.75f, 1.0f};
    c.g = 0.123f;        
    cout << c[1] << " = " << c.g << endl;
    c[1] = 0.321f; // This is legal or UB?
    cout << c[1] << " = " << c.g << endl;
}
Am I violating the standard and invoking undefined behavior by doing the above? Assuming no out-of-range indices are provided of course.
Since r is the first member, I don't know if 6.7.2 part 4.3 gives me further comfort in the fact that I'm referencing the first member in a safe way or not.
 
    