Да, это неопределенное поведение.
Элементы данных не находятся в массиве, и, следовательно, НЕ гарантированно хранятся в непрерывной памяти, как того требует арифметика указателей. Между ними может быть сгенерировано неопределенное заполнение.
Правильным способом будет доступ к элементам по отдельности, например:
double& Tensor::operator[](int i)
{
switch (i)
{
case 0: return XX;
case 1: return XY;
case 2: return XZ;
case 3: return YX;
case 4: return YY;
case 5: return YZ;
case 6: return ZX;
case 7: return ZY;
case 8: return ZZ;
default: throw std::out_of_range("invalid index");
}
}
В качестве альтернативы, если вы действительно хотите использовать синтаксис массива:
double& Tensor::operator[](int i)
{
if ((i < 0) || (i > 8))
throw std::out_of_range("invalid index");
double* arr[] = {
&XX, &XY, &XZ,
&YX, &YY, &YZ,
&ZX, &ZY, &ZZ
};
return *(arr[i]);
}
Или
double& Tensor::operator[](int i)
{
if ((i < 0) || (i > 8))
throw std::out_of_range("invalid index");
static double Tensor::* arr[] = {
&Tensor::XX, &Tensor::XY, &Tensor::XZ,
&Tensor::YX, &Tensor::YY, &Tensor::YZ,
&Tensor::ZX, &Tensor::ZY, &Tensor::ZZ
};
return this->*(arr[i]);
}
В противном случае используйте фактический массив для данных и определите методы для доступа к элементам:
struct Tensor
{
double data[9];
double& XX() { return data[0]; }
double& XY() { return data[1]; }
double& XZ() { return data[2]; }
double& YX() { return data[3]; }
double& YY() { return data[4]; }
double& YZ() { return data[5]; }
double& ZX() { return data[6]; }
double& ZY() { return data[7]; }
double& ZZ() { return data[8]; }
double& operator[](int i)
{
if ((i < 0) || (i > 8))
throw std::out_of_range("invalid index");
return data[i];
}
};