Date: Thu, 23 May 2024 10:57:37 -0400
On Thu, May 23, 2024 at 10:28 AM Maciej Cencora via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> 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:
https://godbolt.org/z/e9vYxTr8E
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.
https://godbolt.org/z/K5514n1hE
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*.
https://godbolt.org/z/czej7exGs
#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
std-proposals_at_[hidden]> wrote:
> 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:
https://godbolt.org/z/e9vYxTr8E
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.
https://godbolt.org/z/K5514n1hE
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*.
https://godbolt.org/z/czej7exGs
#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
Received on 2024-05-23 14:57:51