Skip to content

rfl::replace

rfl::replace creates a deep copy of the original struct or moves the original struct, replacing one or several fields in the process.

This only works for the rfl::Field-syntax.

In some cases, we really only want to change one or a few fields, to get from one struct to another:

const auto lisa = Person{
    .first_name = "Lisa",
    .last_name = "Simpson",
    .children = std::vector<Person>()
};

// Returns a deep copy of the original object,
// replacing first_name.
const auto maggie =
    rfl::replace(lisa, rfl::make_field<"first_name">(std::string("Maggie")));

maggie is now a deep copy of lisa, but with a new first_name.

However, in some cases, we do not want or are unable to create a deep copy of a struct.

For instance, suppose we had put the field children into a rfl::Box:

struct Person {
    std::string first_name;
    std::string last_name;
    rfl::Box<std::vector<Person>> children;
};

rfl::Box cannot be copied, and if we naively try to apply rfl::replace to this, this will not compile (disabling copies is very much the point of rfl::Box).

However, we can use std::move:

auto lisa = Person{.first_name = "Lisa",
                   .last_name = "Simpson",
                   .children = rfl::make_box<std::vector<Person>>()};

const auto maggie = rfl::replace(
    std::move(lisa), rfl::make_field<"firstName">(std::string("Maggie")));

The fields from lisa have now been moved into maggie.

We can also remove several fields using replace:

auto lisa = Person{.first_name = "Lisa",
                   .last_name = "Simpson",
                   .children = rfl::make_box<std::vector<Person>>()};

const auto milhouse = rfl::replace(
    std::move(lisa),
    rfl::make_field<"firstName">(std::string("Maggie")),
    rfl::make_field<"lastName">(std::string("Van Houten")));

If you have nested structs using rfl::Flatten, you can treat the fields inside rfl::Flatten as if they were on the main level:

struct Person {
    std::string first_name;
    rfl::Box<std::string> last_name;
    int age;
};

struct Employee {
    rfl::Flatten<Person> person;
    rfl::Box<std::string> employer;
    float salary;
};

auto employee = Employee{
    .person = Person{.first_name = "Homer",
                     .last_name = rfl::make_box<std::string>("Simpson"),
                     .age = 45},
    .employer = rfl::make_box<std::string>("Mr. Burns"),
    .salary = 60000.0};

auto employee2 =
    rfl::replace(std::move(employee), rfl::make_field<"salary">(70000.0),
                 rfl::make_field<"age">(46));
````

In this case `age` is part of `Person` and `salary` part ot `Employee`, but
because you have flattened them, you can treat them as if they were on
the same level.

You can also replace structs with other structs. Consider the following example:

```cpp
struct Person {
    std::string first_name;
    std::string last_name;
    int age;
};

struct Employee {
    rfl::Flatten<Person> person;
    std::string employer;
    float salary;
};

const auto employee = Employee{
    .person =
        Person{.first_name = "Homer", .last_name = "Simpson", .age = 45},
    .employer = std::string("Mr. Burns"),
    .salary = 60000.0};

const auto carl = Person{.first_name = "Carl", .last_name = "", .age = 45};

const auto employee2 = rfl::replace(employee, carl);

This code flattens the employee structs and then replaces all relevant fields with their counterparts contained in carl.

Finally, we get this JSON string:

{"first_name":"Carl","last_name":"","age":45,"employer":"Mr. Burns","salary":60000.0}

Don't worry, this is fairly optimized and makes heavy use of forwarding. It does not make any copies than it absolutely has to.