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:

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.