I'm writing code that passes around a lot of multi-parameter variables. For example, I might pass an "orientation," which is six doubles (three Cartesian coordinates and three rotations about the axes). It would be reasonable to define an Orientation struct and use that as an argument type. However, due to API limitations, these parameters must be stored as, and often passed to functions as, pointers to arrays of parameters:
// The sane version, using a struct
double distance_from_origin(const Orientation & o) {
return sqrt(o.x * o.x + o.y * o.y + o.z * o.z);
}
// The version I must write due to API constraints
double distance_from_origin(const double * const p) {
return sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
}
Obviously, this is error-prone. I have three potential solutions, with one favorite.
Solution 1
I can use #define or const globals, in a header somewhere, to alias names to indexes.
const size_t x = 0;
const size_t y = 1;
const size_t z = 2;
double distance_from_origin(const double * const p) {
return sqrt(p[x] * p[x] + p[y] * p[y] + p[z] * p[z]);
}
This makes sure x
is always consistent, but pollutes the global namespace. I could hide it in a namespace, but then it's more awkward to use.
Solution 2
An idea previously mentioned here:
struct Orientation {double x, y, z, rot_x, rot_y, rot_z};
Orientation& asOrientation(double * p) {
return *reinterpret_cast<Orientation*>(p);
}
double distance_from_origin(const double * const p) {
Orientation& o = asOrientation(p)
return sqrt(o.x * o.x + o.y * o.y + o.z * o.z);
}
This has nicer syntax, but relies on the rules of C/C++ struct packing. I think that it's safe as long as Orientation
is a POD. I'm nervous about relying on that.
Solution 3
struct Orientation {
Orientation(double * p): x{p[0]}, y{p[1]}, z{p[2]}, rot_x{p[3]},
rot_y{p[4]}, rot_z{p[5]} {}
double &x, &y, &z, &rot_x, &rot_y, &rot_z
};
double distance_from_origin(const double * const p) {
Orientation o{p};
return sqrt(o.x * o.x + o.y * o.y + o.z * o.z);
}
This no longer relies on struct-packing rules, and has nice syntax. However, it relies on compiler optimizations to ensure that it has zero overhead.
Solution 4
Based on this comment by GManNickG.
constexpr double& x(double * p) {return p[0];}
constexpr double& y(double * p) {return p[1];}
constexpr double& z(double * p) {return p[2];}
// ... etc.
double distance_from_origin(const double * const p) {
return sqrt(x(p) * x(p) + y(p) * y(p) + z(p) * z(p));
}
Questions
Solution 3 seems like the best to me. Does it have a potential downside that I'm missing, beyond reliance on compiler optimization?
Is there another solution that's superior to any of these three?