Search code examples
c++constantsimmutabilitymutable

Does C++ support STL-compatible immutable record types?


A number of languages have support for immutable types – once you have constructed the value of such a type, it cannot be modified in any way. I won't digress into the benefits of such types, as this has been discussed extensively elsewhere. (See e.g. http://codebetter.com/patricksmacchia/2008/01/13/immutable-types-understand-them-and-use-them/ )

I would like to create a lightweight immutable record type in C++. An obvious way of doing this would be to const all the members. For example:

struct Coordinates {
    const double x;
    const double y;
};

Unfortunately, a type declared in this way doesn't support copy assignment, and as such can't be used with STL containers, which is a pretty fatal drawback. I don't think there is any way around this, but would be grateful to know if anyone can see one.

For what it's worth, as far as I can see, C++ is conflating two things: a) whether you can assign a new value to a variable, and b) whether "values" (=objects) can be modified after they are constructed. The distinction is much clearer in e.g. Scala, where one has

  • val vs var: a var can have a new value bound to it, a val cannot
  • immutable and mutable objects, particularly collections

So I can write the following:

val val_mutable = collection.mutable.Map(1 -> "One")
val val_immutable = collection.immutable.Map(1 -> "One")
var var_mutable = collection.mutable.Map(1 -> "One")
var var_immutable = collection.immutable.Map(1 -> "One")

vars can be rebound to point at other values:

//FAILS: val_immutable = collection.immutable.Map(2 -> "Two")
//FAILS: val_mutable = collection.mutable.Map(2 -> "Two")
var_mutable = collection.mutable.Map(2 -> "Two")
var_immutable = collection.immutable.Map(2 -> "Two")

mutable collections can be modified:

val_mutable(2) = "Two"    
//FAILS: val_immutable(2) = "Two"    
var_mutable(2) = "Two"    
//FAILS: var_immutable(2) = "Two"    

In C++, consting the data members of a type makes it an immutable type but also makes it impossible to create "var"s of that type. Can anyone can see a reasonably lightweight way of achieving the former without the latter? [Please note the "lightweight" part -- in particular, I do not want to create an accessor function for every element of the struct, as this results in a massive drop in readability.]


Solution

  • You mention that you can't use the Coordinates class with STL containers as written with const members. The question of how to do that is answered (inconclusively) here:

    https://stackoverflow.com/a/3372966/393816

    The same suggestion, which is to make your instances const rather than your members, has been made in comment threads on this question too. I agree with your comment that it is less reliable since you must change more code, but perhaps you could use a typedef like so:

    struct MutableCoordinates {
      double x;
      double y;
    };
    
    typedef const MutableCoordinates Coordinates;
    

    So that users of Coordinates objects automatically get the immutable version unless they explicitly as for it? This would offset some of your worry about the verbosity of using the struct safely.

    Of course, you can also switch to reference semantics by using shared_ptr, which will work very well with const objects since the different references can't mess with each other, but it comes with the overhead of manual memory allocation which is rather excessive for a struct of two doubles.