C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Stop gap required for NRVO until Anton's paper is assimilated

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Sat, 13 Jul 2024 10:22:59 -0400
On Fri, Jul 12, 2024 at 4:50 AM Tiago Freire <tmiguelf_at_[hidden]> wrote:
>
>
> > You can get it "by default" if the implementation allows it. Why should implementations be forced to allow it even when a user doesn't care if it happens or not?
>
> Because it makes everybody's code more efficient.

But it doesn't make their code more efficient, because most
implementations would have made it "more efficient" *anyway*. That's
my point. You're enforcing a thing that would have likely happened
anyway, so what's the point?

Enforcement only matters if the user has written code that needs to
rely on the thing being enforced. Guaranteed elision was not added to
the language to force compilers to agree to when copy elision would
happen; they were already doing that. It wasn't added to make code
more efficient. It was added to the language so that users could write
code that they couldn't write before (returning objects that can't be
copied).

> It's a long foregone conclusion that C++ does work like assembly, the statements as you write them in the source code do not translate 1-to-1 to machine code, "source code" expresses the "intent" of what you want to do (rather than what actually should happen) and the compiler is free to do anything as long it fulfills the intended effects that are observable.
> The tradeoff here is that we have the ruthless efficiency of a machine armed with the knowledge of thousands of domain experts throwing every known trick in the book to make sure your code runs as fast as possible and uses the least amount of resources, instead of relying on my inexperienced sleep deprived monkey brain to expect to do even a half decent job at it.

But... that's what your thing will actually lead to.

If you write your code in accord with the Byzantine rules that govern
enforced NRVO, then your code is maybe faster or something. But since
those rules are Byzantine and arbitrary, "my inexperienced sleep
deprived monkey brain" may accidently write code that doesn't work
with those rules, and therefore it won't get NRVO. But since "my
inexperienced sleep deprived monkey brain" wasn't *trying* to get NRVO
in the first place, they'll never notice.

So nothing will change relative to the status quo.

> > But what matters is whether I *care* if it happens or not. Remember:
> > NRVO is already an optimization. The only reason to put it in the language is so that a user can write code that *relies* on it and will in some way be incorrect if it doesn't happen.
>
> As I said, if you only want to check if it happens or not, I'm more than happy to accept an attribute that tells me that, but the attribute itself does not have any impact on the code generation itself.
> But you seem to suggest that this attribute should have an impact on the code generation itself.

No, I'm saying it should be legit syntax (not an attribute) that will
guarantee *nothing* if you don't use it. You should not be able to
accidentally use guaranteed NRVO. If you want the guarantee, you have
to say so.

> Let's say we did what you asked, what would that even look like?

There have been many suggestions for the syntax. You could tag the
variable declaration with a keyword that says that it is the
return-value object. All return statements within the scope of that
variable must return exactly that variable, and you can't declare two
variables in the same scope with that keyword.

Annotating the variable itself is important because guaranteed NRVO
means that the lifetime of the object will be extended beyond the
scope of the function.

One of the biggest issues with guaranteed NRVO is that it
fundamentally changes the meaning of the code. A local variable is not
*local* anymore; it *will* persist beyond its evident scope. So
consider this:

```
SomeType *ptr = nullptr;
void some_func(SomeType &v)
{
  ptr = &v;
}

SomeType foo()
{
  SomeType retvar = ...;
  some_func(retvar);
  /*some other code*/
  return retvar;
};

void other()
{
  auto var = foo();
  *ptr = ...;
};
```

Now here's a question: is `*ptr = ...` legal code?

By the rules of C++ as it stands, it most assuredly is not. It is
undefined behavior, because `ptr` was set to point to an object that
is outside of its lifetime. If the compiler employs NRVO in `foo`,
that doesn't change the fact that accessing `ptr` is undefined
behavior by the rules of the language. UB can "work"; `ptr` could
point to `var`. But "working" isn't *required* by the language, and it
can cease to "work" for arbitrary reasons.

Your rules now require that this code works. The user is following the
rules of guaranteed NRVO, so `retvar` *must* persist beyond the scope
of the function call. As such, `ptr` *must* point to `var`.

But, if the programmer does anything in "some other code" that
violates the rules of guaranteed NRVO... then the program ceases to be
well-defined behavior and is UB. The program stops working for
seemingly no reason.

By using an opt-in mechanism, you force the programmer to explicitly
say "I want this object to persist beyond the lifetime of the
variable's scope". That means the standard will provide precisely
that, you can rely on the program's behavior, and if you violate the
rules, the compiler will complain instead of giving you UB.

Received on 2024-07-13 14:23:11