Here is a real-world problem:
Let's say you need to create a serialization framework with generic API like:
template <typename T>
T deserialize();
It should support any user-defined aggregate types and with a single API the framework should support two scenarios:
1) the usual deserialize into new object on stack
auto v = deserialize<T>();
2) deserialize directly into a preallocated specific region of memory without blowing-up the stack on embedded systems for huge T
static T v;
...
new (&v) auto(deserialize<T>());
Thanks to guaranteed copy-ellision introduced in C++17 you can achieve exactly that, with exceptions of arrays.
If T is a struct containing C-style array then you cannot create deserialize specialization that uses RVO:
struct Foo { int a[1]; }
template <>
Foo deserialize()
{
return { .a = deserialize<int[1]>() };
}
This won't compile as C-style arrays cannot be returned from function by value.
You could workaround it by expanding the array initialization with a help of std::index_sequence, like:
template <std::size_t ...Ints>
Foo deserialize(std::index_sequence<Ints...>)
{
return { .a = { (Ints, deserialize<int>())... } };
}
But this is impossible if T has multiple arrays, or if arrays size is big (you hit template instantiations limit on your compiler, and what is worse the compilers don't fold this back into a loop so your binary gets huge).
You may say don't use C-style arrays, but the decision may not be up to you.
And even if you force std::array you get hit by the template instantiations limit and binary bloat anyway.
With NRVO you could have it all.
Regards,
Maciej Cencora