I would like to be able to implement immutable data in C++. In short, given a C++ object in which I would like to modify a member variable, instead of modifying that member in place I would like to get a new copy the object with the member being modified.
To address this, I wrote a small template class to represent the immutable field:
template <typename ParentStruct, typename T>
class ImmutableField {
public:
ParentStruct set(const T& newNalue) const {
ParentStruct dst = *(getParentPointer());
reinterpret_cast<ImmutableField<ParentStruct, T>*>((
reinterpret_cast<uint8_t*>(&dst) + _offsetInParent))->_value = newNalue;
return dst;
}
const T& get() const {
return _value;
}
private:
friend ParentStruct;
ImmutableField(const ParentStruct* parent, const T& init = T()) :
_offsetInParent(reinterpret_cast<const uint8_t*>(this) - reinterpret_cast<const uint8_t*>(parent)),
_value(init) {}
const ParentStruct* getParentPointer() const {
return reinterpret_cast<const ParentStruct*>(
reinterpret_cast<const uint8_t*>(this) - _offsetInParent);
}
T _value;
int64_t _offsetInParent = 0;
};
As template parameter, it takes the type of the class it would belong to, and the type of the value that it stores. It has two public methods, get
and set
. Note that set
is const
and returns a ParentStruct
representing the updated version of ParentStruct.
Now I can, for instance, use it to implement a complex number like this:
double sqr(double x) {return x*x;}
struct ComplexNumber {
ComplexNumber() : real(this), imag(this) {}
ImmutableField<ComplexNumber, double> real;
ImmutableField<ComplexNumber, double> imag;
double abs() const {
return sqrt(sqr(real.get()) + sqr(imag.get()));
}
};
And some basic testing suggests that it works:
#define CHECK(X) if (!(X)) {std::cerr << "Check " #X " failed." << std::endl; abort();}
int main() {
ComplexNumber x = ComplexNumber().real.set(3.0).imag.set(4.0);
CHECK(x.abs() == 5.0);
std::cout << "So far, so good!" << std::endl;
ComplexNumber y = x.real.set(12).imag.set(5);
CHECK(y.abs() == 13.0);
std::cout << "It still works!" << std::endl;
return 0;
}
My question is: Is there a better way to achieve what I am trying to do and is there a library for doing this in a cleaner way? The ImmutableField
class seems extremely hacky with lots of reinterpret_cast
.
real.set(3.0)
syntax? Ausing ComplexNumber = const std::complex<double>;
is likely better