taotree taotree - 3 months ago 15
C++ Question

Avoiding boilerplate creating new immutable instances in C++

I'm working with persistent data structures.

I'll have a complex class that I need to create a new instance but with one or more fields with different values.

struct Data {
int field1; int field2; int field3; // etc.
public:
Data withField2(int newField2) {
return { field1, newField2, field3 };
}
};

int main()
{
Data d = { 1, 2, 3 };
std::cout << d.field2 << std::endl;
Data newD = d.withField2(4);
std::cout << newD.field2 << std::endl;
}


Worst case, I can create a bunch of
withField1(newField1Value)
methods as above but there will be enough fields such that it would get really messy.

Also, I might want new values for multiple fields, so then there could be many more.

So, is there some magic way of saying

Data newData = data.with(field1Name = newField1, field2Name = newField2)


or something similar?

Answer

You cannot do that, but you can do something interesting with pointers to data members:

#include<iostream>
#include<functional>

struct Data {
  int field1; int field2; int field3; // etc.

  template<int Data::*M>
  Data withField(int value) {
    Data cpy = *this;
    cpy.*M = value;
    return cpy;
  }
};

int main()
{
  Data d = { 1, 2, 3 };
  std::cout << d.field2 << std::endl;
  Data newD = d.withField<&Data::field2>(4);
  std::cout << newD.field2 << std::endl;
}

This way you don't have to write all the withField member methods at least.

With a bit of machinery and metaprogramming, maybe you could also define a method that changes a bunch of data fields all at once.

Of course, this works fine as long as your fields are all of type int.
Otherwise, you need more than one method or something like this:

template<typename T, T Data::*M>
Data withField(T value) {
  Data cpy = *this;
  cpy.*M = value;
  return cpy;
}

That has to be invoked as:

Data newD = d.withField<int, &Data::field2>(4);