C++ Immutability – Creating an Immutable Struct in C++

cfunctional programmingimmutability

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.

Best Answer

While the goal of using immutable types is usually laudable, your specific implementation is excessively clever, involves loads of UB, and will fail for any ParentStruct that is not trivially copyable. Furthermore, your field instances are bound to a specific instance. As these fields must not be copied around, you should at least delete copy constructors and assignment operators. Also note that your fields each have a comparatively huge int64_t sized overhead – they are not a zero-cost abstraction! (Also, you should have probably used ptrdiff_t types rather than explicit integer types.)

Part of your conceptual problem is that C++ is a value-oriented language, not a reference-oriented language like Java. In C++, a variable is the object. A variable cannot be re-assigned to a different object, but we can overwrite the contents of the object with a new value. This has direct impact on how we think about immutability. In particular, having mutable values is not immediately problematic. Where we want to disallow changes to a value through a specific reference (such as a variable name), we can mark that reference const.

C++ already has an acceptable way to create a new object with only one field changed: make a copy, then change that field.

#include <iostream>
#include <cmath>

struct ComplexNumber {
  ComplexNumber() : real(), imag() {}
  ComplexNumber(double real, double imag) : real(real), imag(imag) {}

  double real;
  double imag;

  double abs() const {
    return std::sqrt(real*real + imag*imag);
  }
};

#define CHECK(X) if (!(X)) {std::cerr << "Check " #X << " failed." << std::endl; abort();}

int main() {
  const ComplexNumber x = ComplexNumber(3.0, 4.0);
  CHECK(x.abs() == 5.0);

  std::cout << "So far, so good!" << std::endl;

  ComplexNumber y = x;
  y.real = 12;
  y.imag = 5;

  CHECK(y.abs() == 13.0);

  std::cout << "It still works!" << std::endl;

  return 0;
}

If y is supposed to be const, then we can use an immediately-invoked lambda:

const ComplexNumber y = [&]() {
  ComplexNumber y = x;
  y.real = 12;
  y.imag = 5;
  return y;
}();

(which, thanks to copy elision, does not require a copy of inner-y to the outer variable.)

This pattern is also nicely generalizable so that you could write a macro to define setter-methods:

#define SETTER(type, name) \
  auto with_##name(type value) const { \
    auto copy = *this; \
    copy.name = value; \
    return copy; \
  }

Then: const ComplexNumber y = x.with_real(12).with_imag(5) at the cost of an extra copy.

Related Topic