arximboldi / immer

Postmodern immutable and persistent data structures for C++ — value semantics at scale
https://sinusoid.es/immer
Boost Software License 1.0
2.48k stars 177 forks source link

Support for box transients? #104

Open waybeforenow opened 4 years ago

waybeforenow commented 4 years ago

For instance, when modifying a struct wrapped with immer::box, it may be simpler to obtain a mutable instance of the struct rather than wrapping all mutations inside an immer::box::update. Is this possible to do without sacrificing performance?

arximboldi commented 4 years ago

Since there is only one element in the box, it does not really make sense to provide a transient type for it. However, it could make sense, in the case where one is using reference counting, to overload .get() such that the value is moved out of the box when the refcount is 1. You could then do:

// given a `box<T> x`
auto v = std::move(x).get()
v.foo = ...;
v.bar = ...;
x = std::move(v);

Would that satisfy your use-case?

waybeforenow commented 4 years ago

I think that makes sense. I'd just like to leverage copy-on-write, but I don't care about the contents of the box once I start modifying them, so a "copy and overwrite" strategy works here.

I assume that this usage is as efficient as the following?

x = x.update([&](auto v) {
  v.foo = ...;
  v.bar = ...;

  return v;
});
arximboldi commented 4 years ago

it should be as efficient as this (note the std::move):

x = std::move(x).update([&](auto v) {
  v.foo = ...;
  v.bar = ...;

  return v;
});

[Note that this optimization is not done yet, so this might also involve a superfluous copy. It is easy to do though... I might do it over the weekend if I remember.]

I'd just like to leverage copy-on-write, but I don't care about the contents of the box once I start modifying them, so a "copy and overwrite" strategy works here.

Yese, these two proposed syntaxes leverage copy-on-write and would avoid a copy of the contents when the box is unique. Did you have some other syntax or API in mind?

waybeforenow commented 4 years ago

No, I think the API you've proposed makes sense. Thanks :)

tusooa commented 6 months ago

Since there is only one element in the box, it does not really make sense to provide a transient type for it. However, it could make sense, in the case where one is using reference counting, to overload .get() such that the value is moved out of the box when the refcount is 1. You could then do:

// given a `box<T> x`
auto v = std::move(x).get()
v.foo = ...;
v.bar = ...;
x = std::move(v);

Would that satisfy your use-case?

I would very much like this. It allows you to do something like returning something else while modifying the boxed value more conveniently.

std::optional<R> res;
x = std::move(x).update([&](auto v) {
    res = func(v);
    return v;
});
return res.value();

could be rewritten as:

auto v = std::move(x).get();
auto res = func(v);
x = std::move(v);
return res;

See also https://lily-is.land/kazv/libkazv/-/merge_requests/61/diffs#b21da8173c8b3638b5e9e2cbd8c4044182270781_112_121

arximboldi commented 6 months ago

Happy to accept a PR for this one!