mentics
mentics

Reputation: 6999

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?

Upvotes: 4

Views: 99

Answers (2)

skypjack
skypjack

Reputation: 50550

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);

Upvotes: 3

MSalters
MSalters

Reputation: 179887

The problem appears to be that you are reinventing std::tuple. But you didn't reinvent std::get<N>.

Your implementation uses names, and to the compiler they're utterly arbitrary. There's no guarantee the field names occur in numerical order without gasps.

Upvotes: 1

Related Questions