Copy elision permits an implementation to omit copy or move construction even when that constructor or destructor has observable side effects. Before C++17, every elision case was permitted but optional. C++17 changed prvalue semantics, making several former temporary-elision cases direct construction instead.
Returned Prvalues and Named Locals #
Since C++17, a prvalue of the function return type returned from a function constructs the result object directly. A deleted copy and move constructor therefore do not prevent this form, although the returned type’s destructor must still be accessible and non-deleted at the return statement:
struct Token {
Token();
Token(const Token&) = delete;
Token(Token&&) = delete;
};
Token make_token() {
return Token{}; // guaranteed direct construction since C++17
}Named return value optimization (NRVO) remains optional, including in C++17 and later:
struct Widget {};
Widget make_named_widget() {
Widget widget;
return widget; // NRVO may apply; a viable copy or move is required
}When NRVO does not apply, an eligible local in a return statement is treated as an rvalue for overload resolution. Do not write return std::move(token); merely to request that behavior. It can inhibit NRVO and changes the expression from the name eligible for NRVO into an xvalue. Return the named local directly.
Interface and Lifetime Design #
Prefer returning a completed value when the caller receives an independent result:
#include <string>
std::string make_label(int id) {
return "item-" + std::to_string(id);
}Return values are not references to local objects. A reference return is correct only when the referred object outlives the call and borrowing or aliasing is the intended contract:
const Config& active_config(); // refers to storage with a longer lifetime
Never return an lvalue or rvalue reference to an automatic local. An rvalue reference does not extend a local object’s lifetime past the return; it only communicates a reference category. Conversely, binding a temporary to a local const T& or T&& extends that temporary’s lifetime only to the lifetime of that local reference, not to the lifetime of a reference returned from the function.
Write return-by-value interfaces without defensive moves, then measure only a demonstrated cost. C++17’s guarantee applies to the specified prvalue cases; NRVO, base-subobject initialization, and conversions to a different result type remain separate cases with their own copy or move requirements.
Comments
Read or comment on GitHub (sign-in required to comment)