Guaranteed Copy Elision in Modern C++
Copy elision has evolved from an optional compiler optimization into a strict language guarantee within the C++ standard. This article explains how C++ guarantees copy elision by redefining value categories and object initialization, detailing the technical mechanisms introduced in C++17 that ensure unnecessary copy and move operations are entirely eliminated rather than merely optimized away.
From Optimization to Language Semantic
Prior to C++17, copy elision—including Return Value Optimization (RVO)—was an optional transformation permitted by the "as-if" rule. Compilers were allowed, but not required, to omit copy or move constructors when returning a temporary or creating an object directly from a temporary expression. Because the optimization was optional, types still required an accessible, non-deleted copy or move constructor, even if the actual call was never emitted in machine code.
With the adoption of the C++17 standard, copy elision in specific contexts became mandatory. The standard committee achieved this not by forcing compilers to apply an optimization pass, but by redefining the core language semantics concerning how prvalues (pure rvalues) evaluate.
The Mechanism: Redefining Prvalues and Temporary Materialization
The core engine behind guaranteed copy elision is the separation of value evaluation from object creation.
- Prvalues as Initializers: In C++17 and later, a prvalue is no longer considered a temporary object. Instead, it is an expression whose evaluation initializes an object. A prvalue acts as a set of instructions or a recipe for constructing a value, rather than a standalone storage entity.
- Delayed Object Construction: When an object is initialized from a prvalue of the same type, no temporary is created. The target object's memory is allocated first, and the prvalue expression directly initializes the storage of that destination object.
- Temporary Materialization: If a prvalue must be treated as an actual object (such as when binding to a reference or accessing a member), a temporary object is created through a process known as temporary materialization conversion.
Because no temporary exists during direct prvalue initialization, there is fundamentally no copy or move constructor to invoke. The operation is elided by definition.
Guaranteed Contexts
The C++ standard guarantees copy elision under two specific circumstances:
Return by Value of a Prvalue: When a function returns a prvalue whose type matches the function's return type (ignoring top-level cv-qualifiers):
struct Widget {}; Widget make_widget() { return Widget{}; // Guaranteed: No temporary created, initializes the caller's storage }Initialization from a Prvalue: When an object is initialized using a prvalue of the same class type:
Widget w = Widget{}; // Guaranteed: Initializes 'w' directly without temporary construction
In these contexts, the class is not required to have an accessible or defined copy or move constructor. This allows non-copyable, non-movable types to be returned by value from factory functions.
Guaranteed Elision vs. Named Return Value Optimization (NRVO)
It is crucial to distinguish guaranteed copy elision from Named Return Value Optimization (NRVO).
NRVO occurs when a function returns an existing named automatic object:
Widget make_widget_named() {
Widget local_w;
return local_w; // NRVO: Not guaranteed by the standard
}Under NRVO, the compiler evaluates a variable that has already been
declared and allocated in the local scope. While compilers are permitted
to construct local_w directly in the caller's return slot,
this remains an optional optimization. Consequently, types returning via
NRVO must still provide an accessible copy or move constructor to ensure
compliance across all conformant implementations.