Why RVO and NRVO are not made obligatory by the standard? [closed]
Asked Answered
S

1

7

Why RVO and NRVO optimizations are not made obligatory (when they are applicable) by the standard? e.g. there is a very common case when a function produces some object and returns it as the result. The copy/move constructors are usually elided because of RVO/NRVO, but they still need to be defined, which is somewhat confusing. If RVO/NRVO was in the standard, the copy/move constructors would be no longer required in this case.

Synecious answered 30/8, 2013 at 22:37 Comment(10)
Can RVO/NRVO replace all copy/move operations? Would eliminating copy/move break existing code? If copy/move can't be cleanly removed, why .. oh the worms.Purulence
As far as I understand, (N)RVO falls under the "as-if" rule. I believe it makes sense as far as the standard is concerned. And, no, this wouldn't allow you to get completely rid of copy/move constructors, because they are used in a lot of other contexts.Daunt
RVO/NRVO is allowed by the standard, so it's up to the compiler to perform it or not. I'm asking why it's not always required in applicable cases. That would diminish ambiguity which is a good thing.Synecious
Presumably move semantics work in a debug build but RVO doesn't. Additionally move semantics address the perfect forwarding issue.Ware
Of course I mean that copy/move constructors wouldn't be required in this particular case.Synecious
@syam: I don't believe it falls under the "as-if" rule, because it affects whether a constructor is actually called (and a correct program can detect the optimization's presence or absence by adding debugging output to the constructor). Rather, it's a specifically allowed optimization.Ballroom
@Ballroom But a copy/move constructor has very specific semantics... It shouldn't do anything else than just copy/move the object. (ie. it should not have any other side effects). If you make successive temporary copies of an object, only the first and the last one matters. (I hope I am clear enough, I'm getting a bit tired right now...) So really, semantics-wise, it's "as-if" the original object had been constructed where the final object resides.Daunt
@syam: Your appeal to semantics seems weak to me. Technically speaking, if the standard allows an implementation to handle a given program a given way, then that possible handling is part of the program's semantics; so yes, it's true that a copy/move constructor "shouldn't" have other side effects, in that the standard specifies various cases where the constructor can be elided (thereby bypassing the side effects). But that's a circular argument: you're saying that elision is optional because it should be equivalent to non-elision because elision is optional.Ballroom
@syam: (And technically speaking, you're right that it falls under the "as-if" rule, but only because all allowed behaviors fall under the "as-if" rule: that rule is simply a clarification of what it means to be allowed. The optimization is specifically described in paragraph 31 of section 12.8 of the standard, and if it weren't described there, then it would not be allowed, so the "as-if" rule would not cover it.)Ballroom
@Syam: "it's "as-if" the original object had been constructed where the final object resides." You're thinking like a user, not a language lawyer. If behavioral changes can be apparent at all, if the user can detect even the slightest difference in the behavior of their code, then the standard must specify it (or call it "undefined"). If the standard did not specify when copy/moves could be elided, compilers couldn't do it at all. Users can detect the behavior changes of elision, and thus implementations would be against the spec if those behavior changes were not defined.Gosh
G
2

Copy elision is not required by the standard because that would require all implementations to implement it in all cases.

Just look at the case of return-value-optimization vs named-return-value-optimization. Simply turning this:

std::string Func()
{
  return std::string("foo");
}

Into this functionally identical code:

std::string Func()
{
  std::string named("foo");
  return named;
}

The latter requires a lot more out of the compiler than the former. Different compilers support NRVO in different circumstances. Sure, most of them support it in this trivial case, but there are a lot of different cases out there. And there are some cases where compilers just say "screw it" and doesn't do the optimization altogether.

Your way would require one of the following:

  1. To enforce copy elision in all applicable cases, no matter how difficult to implement for compilers. So now every compiler writer has to deal with the cases like this:

    std::string Func(bool b)
    {
      if(b)
      {
        std::string named("foo");
        return named;
      }
      else
      {
        std::string named("bar");
        return named;
      }
    }
    

    Many compilers don't handle NRVO in those cases. And that's a simple case; they can get much more complex than that.

  2. Go through every compiler and find a common subset of cases where copy elision is always used, then specify them in the standard as requirements. That's utterly ludicrous; you'd be standardizing based on implementation details. That's never a good thing.

Note that C++17 may be getting a guarantee of copy elision in a specific case. Namely, elision is required for a copy/move any time a temporary is used to initialize an object of the same type. This makes it possible to return an immobile object from a function.

Gosh answered 31/8, 2013 at 0:53 Comment(4)
It seems to me that a rule of performing NRVO when there's no ambiguity in the named return value (i.e. the returned variable can be determined statically) would be enough, so I don't think it's too many implementation details. And I don't see any cases when pure RVO can't be performed.Synecious
"when there's no ambiguity in the named return value" And what about a compiler that can do it in such cases? You'd still need the current copy elision language in the spec so that those compilers can optimize those cases. At which point, you've gained precious little. You've made the spec more complex all to gain an optimization you probably already have. Nothing in your compiler is improved in any way. So... what's the point?Gosh
How can the compiler perform NRVO when it can't determine which local variable will be returned? Seems impossible to me, at least GCC 4.8.1 is not capable of that, as I saw from running your test. What other compilers can do that?Synecious
@Synecious Whether the named return value can be determined statically will vary from one compiler to the next. For example, inlining may turn non-constant conditions constant, at which the compiler may determine that one of the return statements is unreachable, and apply NRVO to the other.Irkutsk

© 2022 - 2024 — McMap. All rights reserved.