Copy Elision: Why the Copy Constructor Is Never Called

April 23, 2026 · Luciano Muratore

Copy Elision: Why the Copy Constructor Is Never Called

Modern C++ compilers are smarter than they look. One of the places where this becomes most visible—and most surprising—is Copy Elision: the compiler’s ability to omit a copy or move entirely, even when a copy constructor is defined and would otherwise be called.


The Surprising Output

Consider the following code:

#include <iostream>
using namespace std;

class B {
public:
    B(const char* str = "\0") // default constructor
    {
        cout << "Constructor called" << endl;
    }

    B(const B& b) // copy constructor
    {
        cout << "Copy constructor called" << endl;
    }
};

int main()
{
    B ob = "copy me";
    return 0;
}

The output is:

Constructor called

No copy constructor. Just one constructor call. If you expected "Copy constructor called" to appear, you are not wrong to think so—but the compiler disagrees.


What the Compiler Actually Does

The statement:

B ob = "copy me";

looks like an assignment, but it is not. It is an initialization. And according to the standard, the compiler reads this as:

B ob = B("copy me");

Conceptually, this would mean: construct a temporary B from the string literal, then copy that temporary into ob. Two operations. Two constructor calls.

But here is where Copy Elision kicks in. The compiler recognizes that the temporary exists for the sole purpose of initializing ob. There is no reason to create it separately and then copy it—it can construct ob directly in place, using the one-argument constructor. The temporary is elided.


Why the Conversion Happens

The underlying theory is this: when constructing ob, the compiler uses the one-argument constructor to convert "copy me" into a temporary object of type B. That temporary would then be copied into ob via the copy constructor.

But since the temporary has no other purpose—it exists only to become ob—the compiler treats its creation as redundant. Under Copy Elision, constructing the temporary and copying it into the target are collapsed into a single construction. The copy constructor is never invoked.

This is not an optimization the compiler chooses to apply. Since C++17, this form of Copy Elision—known as NRVO in some contexts, and more specifically as prvalue elision here—is mandatory. The compiler is not skipping a step as a courtesy; it is following the standard.


Does This Make Sense?

It does, once you accept the core intuition: if a temporary object exists only to be immediately copied into a target, then the temporary and the target are the same thing. There is no duplication—there is only construction. And if there is only construction, the copy constructor has nothing to do.

The restriction on unnecessary copies does not just eliminate a performance cost. It makes the semantics cleaner: the temporary was always just a way of describing how to build the object, not a real, independent object in its own right.


Summary

  • B ob = "copy me" is interpreted by the compiler as B ob = B("copy me").
  • Conceptually, this would construct a temporary and then copy it—two operations.
  • Copy Elision collapses these into one: the object is constructed in place, directly.
  • The copy constructor is never called because the temporary it would copy from is never materialized.
  • Since C++17, this form of elision is not optional—it is mandated by the standard.
  • Final Insight: Copy Elision is not the compiler cutting corners. It is the compiler recognizing that a temporary created only to initialize something else is not really a separate object—it is just the object, described before it has a name.