Search code examples
c++visual-studiolanguage-lawyerc++20return-value-optimization

How to enforce copy elision in C++20?


C++17 promised to introduce Copy Elision as a requirement, so I've upgraded from C++14 all the way to C++20. Just for that. (RVO as an optional behavior-altering optimization... makes me genuinely motion-sick when running a program in my head.) I'm very new to this version of C++, but I want to prescribe the behavior of returning an object completely; whether it calls it's copy method and a temp's destructor, or does not.

Object f() {
    Object x;
    x.value = 10;
    x.str = "example function";
    return x;
}

Is there anything special I must do (or change) in this f() function to ensure the returned object always elides calling it's copy or move constructor and x's destructor? Also, is there any way to ask the compiler to abort compilation if it can't do so? I don't want to accidentally give the compiler the ability to choose, if I can help it.


Solution

  • Check out cppreference's page on copy elision. Since C++17, there is a guaranteed form and a non-mandatory form. Technically, guaranteed copy elision is not even considered copy elision anymore, it is simply a change in the specification of prvalues.

    "Guaranteed copy elision" happens when initializing an object (including in a return statement) with a prvalue of the same class type (ignoring cv-qualification). By definition, a prvalue has no name, and so, before C++17 and in the case of a return statement, this was an non-mandatory optimization called unnamed return value optimization (URVO). Since C++17, it is replaced by a fundamental change in the language:

    a prvalue is not materialized until needed, and then it is constructed directly into the storage of its final destination.

    In your code, x is named, so it is not a prvalue, and as such its copy (or move) is not guaranteed to be elided. Nevertheless, I believe most modern compilers will perform copy elision in this case, unless you specifically ask them not to (for example with -fno-elide-constructors for GCC and Clang). It is called named return value optimization (NRVO), and it is one of only two optimizations that may change the observable side-effects. Note that NRVO is forbidden in the context of a constant expression.

    If you want a guarantee that no copy or move is performed, then you need to handle a prvalue instead:

    Object f() {
        int value = 10;
        std::string str = "example function";
        return Object(value, str); // this is a prvalue being returned
    }
    
    // simpler
    Object f() {
        return Object(10, "example function");
    }
    
    // even simpler if Object's constructor is implicit
    Object f() {
        return {10, "example function"};
    }