Copy Elision: Guaranteed and Merely Likely
advanced9 min readLesson 136 of 204
C++17 made returning a prvalue free by definition. NRVO is still a courtesy the compiler usually grants — and std::move(local) disables it.
Since C++17, returning a prvalue is guaranteed elision: no copy or move exists even conceptually. The object is constructed directly in its final home:
std::string build() { return std::string(1000, 'x'); } // zero copies, zero moves — by the language
auto s = build(); // one allocation, one string
NRVO — the powerful courtesy
Returning a named local (NRVO) is not guaranteed, but mainstream compilers do it for simple flows:
std::string accumulate(int n) {
std::string out; // named local: NRVO candidate
for (int i = 0; i < n; ++i) out += 'a';
return out; // usually constructed directly in the caller's storage
}
The anti-pattern: return std::move(local)
return std::move(local); disables NRVO (the expression is no longer the local's name) and forces a move — strictly worse than return local;, which moves anyway when elision fails. Compilers flag it (-Wpessimizing-move).
Where elision never applies
- Assignment (only initialization elides),
- parameters (they are copies or references from the start),
- returning a member or parameter (only a local's NRVO or a prvalue return elides).
Count constructions with an instrumented type in the practice — the counters are the proof.