Reputation: 148965
This is a C++ followup for another question of mine
In the old days of pre-ISO C, the following code would have surprised nobody:
struct Point {
double x;
double y;
double z;
};
double dist(struct Point *p1, struct Point *p2) {
double d2 = 0;
double *coord1 = &p1->x;
double *coord2 = &p2->x;
int i;
for (i=0; i<3; i++) {
double d = coord2[i] - coord1[i]; // THE problem
d2 += d * d;
}
return sqrt(d2);
}
Unfortunately, this problematic line uses pointer arithmetic (p[i]
being by definition *(p + i))
outside of any array which is explicitely not allowed by the standard. Draft 4659 for C++17 says in 8.7 [expr.add]:
If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i + j] if 0 <= i + j <= n; otherwise, the behavior is undefined.
And the (non-normative) note 86 makes it even more explicit:
An object that is not an array element is considered to belong to a single-element array for this purpose. A pointer past the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical element x[n] for this purpose.
The accepted answer of the referenced question uses the fact that the C language accepts type punning through unions, but I could never find the equivalent in the C++ standard. So I assume that a union containing an anonymous struct member and an array would lead to Undefined Behaviour
in C++ — they are different languages...
What could be a conformant way to iterate through members of a struct as if they were members of an array in C++? I am searching for a way in current (C++17) versions, but solutions for older versions are also welcome.
It obviously only applies to elements of same type, and padding can be detected with a simple assert
as shown in that other question, so padding, alignment, and mixed types are not my problem here.
Upvotes: 32
Views: 3531
Reputation: 4132
What about redefining struct Point
to something like this:
struct Point {
double coords[3];
double& x = coords[0];
double& y = coords[1];
double& z = coords[2];
};
You could then do things like this:
Point p0 { 3, 7, 2 };
std::cout << "x: " << p0.x << "\n";
std::cout << "y: " << p0.y << "\n";
std::cout << "z: " << p0.z << "\n";
for (int axis=0; axis<3; axis++) {
std::cout << "axis#" << axis << ": " << p0.coords[axis] << "\n";
}
IDK how well this conforms to the C++ standards.
Note: compiled on MSVC++ 19.42.34435 (VS 2022) with /std:c++17
Upvotes: 0
Reputation: 5137
IMHO the easiest way is to just implement operator[]
. You can make a helper array like this or just create a switch...
struct Point
{
double const& operator[] (std::size_t i) const
{
const std::array coords {&x, &y, &z};
return *coords[i];
}
double& operator[] (std::size_t i)
{
const std::array coords {&x, &y, &z};
return *coords[i];
}
double x;
double y;
double z;
};
int main()
{
Point p {1, 2, 3};
std::cout << p[2] - p[1];
return 0;
}
Upvotes: 18
Reputation: 18051
You could use the fact that casting a pointer to intptr_t
doing arithmetic and then casting the value back to the pointer type is implemetation defined behavior. I believe it will work on most of the compilers:
template<class T>
T* increment_pointer(T* a){
return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(a)+sizeof(T));
}
This technic is the most efficient, optimizers seems not to be able to produce optimal if one use table look up: assemblies-comparison
Upvotes: 0
Reputation: 275510
struct Point {
double x;
double y;
double z;
double& operator[]( std::size_t i ) {
auto self = reinterpret_cast<uintptr_t>( this );
auto v = self+i*sizeof(double);
return *reinterpret_cast<double*>(v);
}
double const& operator[]( std::size_t i ) const {
auto self = reinterpret_cast<uintptr_t>( this );
auto v = self+i*sizeof(double);
return *reinterpret_cast<double const*>(v);
}
};
this relies on there being no packing between the double
s in your `struct. Asserting that is difficult.
A POD struct is a sequence of bytes guaranteed.
A compiler should be able to compile []
down to the same instructions (or lack thereof) as a raw array access or pointer arithmetic. There may be some problems where this optimization happens "too late" for other optimzations to occur, so double-check in performance sensitive code.
It is possible that converting to char*
or std::byte*
insted of uintptr_t
would be valid, but there is a core issue about if pointer arithmetic is permitted in this case.
Upvotes: 2
Reputation: 2749
Use an constexpr array of pointer-to-member:
#include <math.h>
struct Point {
double x;
double y;
double z;
};
double dist(struct Point *p1, struct Point *p2) {
constexpr double Point::* coords[3] = {&Point::x, &Point::y, &Point::z};
double d2 = 0;
for (int i=0; i<3; i++) {
double d = p1->*coords[i] - p2->*coords[i];
d2 += d * d;
}
return sqrt(d2);
}
Upvotes: 28