Смещение структуры и массива C++
Это C++ наблюдение за другим вопросом моего
В прежние дни до ISO C следующий код никого не удивил бы:
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);
}
К сожалению, эта проблемная строка использует арифметику указателя (p[i]
по определению *(p + i))
вне любого массива, который явно не допускается стандартом. Проект 4659 для C++ 17 говорит в 8.7 [expr.add]:
Если выражение P указывает на элемент x [i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j) указывают на (возможно-гипотетический) элемент x [i + j], если 0 <= я + j <= n; в противном случае поведение не определено.
И (ненормативная) заметка 86 делает ее еще более явной:
Для этой цели считается, что объект, который не является элементом массива, относится к одноэлементному массиву. Указатель, предшествующий последнему элементу массива x из n элементов, считается эквивалентным указателю на гипотетический элемент x [n] для этой цели.
В принятом ответе упомянутого вопроса используется тот факт, что язык C принимает тип, пишущий через союзы, но я никогда не мог найти эквивалент в стандарте C++. Поэтому я предполагаю, что объединение, содержащее анонимный элемент структуры и массив, приведет к Undefined Behaviour
в C++ - это разные языки...
Вопрос:
Что может быть подходящим способом для итерации через члены структуры, как если бы они были членами массива в C++? Я ищу способ в текущих версиях (C++ 17), но также приветствуются решения для более старых версий.
Отказ от ответственности:
Очевидно, что это относится только к элементам того же типа, и отступы могут быть обнаружены с помощью простого assert
как показано в этом другом вопросе, поэтому дополнения, выравнивание и смешанные типы здесь не являются моей проблемой.
Ответы
Ответ 1
Используйте массив constexpr от указателя к элементу:
#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);
}
Ответ 2
ИМХО самым простым способом является просто реализовать operator[]
. Вы можете создать вспомогательный массив, подобный этому, или просто создать переключатель...
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;
}
Ответ 3
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);
}
};
это зависит от отсутствия упаковки между double
в вашей структуре. Утверждение, что это сложно.
Структура POD - это гарантированная последовательность байтов.
Компилятор должен иметь возможность компилировать []
до тех же инструкций (или их отсутствия) в качестве доступа к необработанному массиву или арифметики указателя. Могут быть некоторые проблемы, когда эта оптимизация происходит "слишком поздно" для других оптимизаций, поэтому необходимо дважды проверить код, чувствительный к производительности.
Возможно, что преобразование в char*
или std::byte*
insted из uintptr_t
было бы правильным, но есть основная проблема, если в этом случае разрешена арифметика указателя.
Ответ 4
Вы могли бы использовать тот факт, что приведение указателя к intptr_t
выполняющего арифметику, а затем intptr_t
значения обратно к типу указателя - это поведение, определяемое реализацией. Я считаю, что он будет работать на большинстве компиляторов:
template<class T>
T* increment_pointer(T* a){
return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(a)+sizeof(T));
}
Эта техника является наиболее эффективной, оптимизаторы, похоже, не могут создать оптимальные, если использовать таблицу поиска: сборка-сравнение