A use case for RVO
In a coroutines runtime library I have an array of atomics:
struct Block {
array<atomic<uint64_t>, 8> costacks;
};
I need to construct a Block object and initialize the atomics with iota(a, a + 8).
One way to do it is:
explicit Block(uint64_t a) {
for (auto i: views::iota(0, 8)) {
costacks[i].store(i + a);
}
}
but then we would pay for a store, which is wasteful. To be precise, this is a re-initialization rahter than an initialization, because at this point the atomics are already initialized and we have missed the opportunity to supply proper initial values.
We can avoid this thanks to RVO:
explicit Block(uint64_t a)
: costacks{[a]<uint64_t... I>(std::integer_sequence<uint64_t, I...>) {
return std::array<std::atomic<uint64_t>, 8>{(a + I)...};
}(std::make_integer_sequence<uint64_t, 8>())} {
}
RVO optimizations take place in return expressions. They result in creating the object right in the destination memory location on the caller side. This avoids implicit boilerplate code to move or copy the object from the source (inside the function) to the destination (outside the function). Thre are two flavors of RVO - Unnamed-RVO and Named-RVO. The Unnamed one is required by the language, while the Named one is only encouraged by the language.
For my example this optimization makes possible to initialize the array of atomics in a function and put the result in a class data member without involving neither copy nor move operations, which std::atomic lacks.
But why we need a function at all? In fact, we don’t need a function here, and we don’t need this feature either. We can simplify the code by not using a feature (generic initialization), which in turn eliminates the need for another feature (RVO) to solve the problem that would have been created by the first feature. I simply can do:
explicit Block(uint64_t a)
: costacks{a, a + 1, a + 2, a + 3, a + 4, a + 5, a + 6, a + 7} {
}