What kind of semantic ambiguities are you talking about?
I've
provided you with a specific example where without NRVO you cannot
construct and return big arrays or structs thereof with a guarantee that
it won't blow up the stack.
Using std::array doesn't solve the issue (just gets rid of one out of three problems there).
This is another case of leading with complications ("can't return C array from function") that distract from the example. :)
Here's a simpler case:
constexpr int N = 1'000'000'000;
std::array<long, N> getFooedArray(int *p) {
std::array<long, N> r = {};
for (int i=0; i < N; i += 1'000'000) { r[i] = 1; }
return std::move(r);
}
std::array<long, N> *allocateFooedArray(int *p) {
return new auto(getFooedArray(p));
}
With `return std::move(r)`, this forces `r` to be stack-allocated, which then refuses to compile because it would totally blow your stack segment.
But change it to `return r;` and everyone's happy, because `r` no longer requires its own stack-allocation; now it's RVO'ed.
But using raw `std::array` is an antipattern; let's wrap it in a strong class type of our own.
And then, since `Widget` is designed never to be copied or moved, let's enforce that.
In fact — reintroducing threading to the mix — we can go ahead and place a std::mutex at the head of the class; this is fine, since (again) `Widget` is immobile.
And then boom goes the dynamite: despite the programmer wanting to enforce that `Widget` is immobile — never moved or copied — it turns out that we must provide a dummy declaration for our copy (or move) constructor, and rely on the linker to catch any attempt by the client programmer to call that dummy constructor. "Dummy declaration in place of =delete" is C++98-era technology! We ought to be able to do better in the core language.
#include <mutex>
constexpr int N = 1'000'000'000;
struct Widget {
explicit Widget() = default;
void foo() { for (int i=0; i < N; i += 1'000'000) data_[i] = 1; }
// Never copy or move a Widget -- it's too expensive!
Widget(const Widget&); // cannot =delete, but this member doesn't really exist;
// trying to call it will give you a linker error
Widget& operator=(const Widget&) = delete;
private:
std::mutex m_;
long data_[N] = {};
};
Widget getFooedWidget(int *p) {
Widget r;
r.foo();
return r;
}
Widget *allocateFooedWidget(int *p) {
return new Widget(getFooedWidget(p));
}
int main() {
Widget *p = allocateFooedWidget(nullptr);
delete p;
}
–Arthur