In some data structures, it would be useful to have members whose values are computed from the other data members upon access instead of stored.
For example, a typical rect class might store it's left, top, right and bottom coordinates in member data fields, and provide getter methods that return the computed width and height based on those values, for clients which require the relative dimensions instead of the absolute positions.
struct rect
{
int left, top, right, bottom;
// ...
int get_width() const { return right - left; }
int get_height() const { return bottom - top; }
};
This implementation allows us to get and set the absolute coordinates of the rectangles sides,
float center_y = (float)(box.top + box.bottom) / 2.0;
and additionally to get it's relative dimensions, albeit using the slightly different method-call operator expression syntax:
float aspect = (float)box.get_width() / (float)box.get_height();
The Problem
One could argue, however, that it is equally valid to store the relative width and height instead of absolute right and bottom coordinates, and require clients that need to compute the right and bottom values to use getter methods.
My Solution
In order to avoid the need to remember which case requires method call vs. data member access operator syntax, I have come up with some code that works in the current stable gcc and clang compilers. Here is a fully functional example implementation of a rect data structure:
#include <iostream>
struct rect
{
union {
struct {
union { int l; int left; };
union { int t; int top; };
union { int r; int right; };
union { int b; int bot; int bottom; };
};
struct {
operator int() {
return ((rect*)this)->r - ((rect*)this)->l;
}
} w, width;
struct {
operator int() {
return ((rect*)this)->b - ((rect*)this)->t;
}
} h, height;
};
rect(): l(0), t(0), r(0), b(0) {}
rect(int _w, int _h): l(0), t(0), r(_w), b(_h) {}
rect(int _l, int _t, int _r, int _b): l(_l), t(_t), r(_r), b(_b) {}
template<class OStream> friend OStream& operator<<(OStream& out, const rect& ref)
{
return out << "rect(left=" << ref.l << ", top=" << ref.t << ", right=" << ref.r << ", bottom=" << ref.b << ")";
}
};
/// @brief Small test program showing that rect.w and rect.h behave like data members
int main()
{
rect t(3, 5, 103, 30);
std::cout << "sizeof(rect) is " << sizeof(rect) << std::endl;
std::cout << "t is " << t << std::endl;
std::cout << "t.w is " << t.w << std::endl;
std::cout << "t.h is " << t.h << std::endl;
return 0;
}
Is there anything wrong with what I am doing here?
Something about the pointer-casts in the nested empty struct types' implicit conversion operators, i.e. these lines:
return ((rect*)this)->r - ((rect*)this)->l;
feels dirty, as though I may be violating good C++ style convention. If this or some other aspect of my solution is wrong, I'd like to know what the reasoning is, and ultimately, if this is bad practice then is there a valid way to achieve the same results.