April 26, 2023·6 min read·engineeringcppgccmove-semanticscpp23

GCC 13 replaces the implicit-move fallback with an xvalue rule

GCC 13 implements C++23's simpler return-expression rules and adds library facilities whose availability remains distinct from language support.

A return statement has historically asked overload resolution two questions about the same local object. It first tries the object as an rvalue and can then retry it as an lvalue. C++23 replaces that mechanism for eligible expressions with a direct value-category rule, and GCC 13 implements the change.

The GCC 13 release page dates GCC 13.1 to April 26. Its change notes list Arthur O'Dwyer's P2266R3, simpler implicit move, among the implemented C++23 language proposals. The same release adds substantial library work, including experimental formatting support and new range facilities.

I am sympathetic to a change that removes a second interpretation of an expression. C++'s move machinery is useful, but a large codebase already asks its readers to recognize several generations of language rules. A more direct account of a returned local object reduces one source of that historical complexity.

The old fallback was a specification mechanism

P2266R3 describes how implicit move grew through earlier standards and defect resolutions. Returning a move-only value requires the language to treat an eligible local source differently from an arbitrary named lvalue. Otherwise, naming the source could prevent the move operation needed to return it.

The earlier mechanism first performed overload resolution as though the returned expression were an rvalue. If that resolution failed, another attempt could consider it as an lvalue. That fallback made some code work, but the precise meaning of failure became consequential.

Selecting a deleted function is not the same as failing to find a unique best candidate. The paper documents implementation disagreement about such cases. That distinction is easy to miss if implicit move is taught only as an informal preference for moving when possible.

The new rule instead makes a move-eligible identifier expression an xvalue. Overload resolution then works with that category. There is no general second attempt that reinterprets the same eligible source as an lvalue merely because the first interpretation did not produce a usable result.

This is a language rule about a specified set of contexts and entities. It is not a request to treat every named variable as expiring. Ordinary uses of a named object still need their ordinary value categories, and the eligibility conditions identify where the new treatment applies.

Reference returns exposed an inconsistency

The proposal identifies a gap in the earlier wording involving functions returning references. Some of the implicit-move rules were tied to initializing a return object, so a reference return could miss treatment that a value return received.

A simple C++23 example shows the corrected relationship:

struct item {};

item&& relay(item&& value)
{
    return value;
}

The parameter is an rvalue reference to an object, and its use as the eligible return operand becomes an xvalue under the revised rule. The example expresses the return-category behavior. It does not extend the lifetime of the referred-to object.

That last qualification matters. Returning a reference is still a reference operation. If a caller supplies an object that soon dies, the returned reference does not create ownership or make that object live indefinitely. A rule that permits the return expression is different from a guarantee about the lifetime of its eventual use.

The paper also discusses decltype(auto). Because that form can depend on an expression's category, changing an eligible return expression to an xvalue can change the deduced reference type. Parentheses remain significant in this area, so this is a semantic change that can affect source compatibility.

For me, the useful simplification is that the value category now explains the result. The programmer still has to reason about references, but does not also need to reconstruct whether the function happens to initialize a return object before the move rule applies.

Removing fallback rejects some old code

The proposal explicitly shows cases that become ill-formed. If a result type can be constructed only from a mutable lvalue reference, an eligible local returned directly no longer gets a fallback lvalue interpretation. The code must express the intended construction in another way if that behavior is required.

This can expose unsafe source. P2266R3 discusses a function returning a reference_wrapper to a local object. Earlier fallback behavior could allow construction of a wrapper that outlived the object it referenced. The new rule rejects that direct return in the example.

That benefit should not be generalized into complete dangling-reference detection. The proposal concerns eligibility and overload resolution. A programmer can still express invalid lifetime relationships elsewhere, including through interfaces whose signatures cannot describe ownership.

The paper's implementation-experience section also records legitimate compatibility adjustments. Some code explicitly casts to an lvalue reference to preserve its intended behavior. Another example makes a conversion explicit at the return site. These changes state an interpretation that the previous fallback had supplied implicitly.

An explicit operation can therefore be useful when a program intentionally needs the old interpretation. The maintenance question is what that interpretation means for the object involved. Restoring compilation alone does not establish that a returned reference remains valid.

Copy elision is a different consideration

The proposal places the new account in the rules for identifier expressions rather than leaving it only in the discussion of copy elision. That helps distinguish the expression's category from whether an implementation can omit a copy or move operation.

A return can have a valid type and category relationship even when an operation is later elided. Conversely, the possibility of elision does not make every invalid constructor selection acceptable. The paper's revised model describes the source expression independently enough to support those distinctions.

This is relevant when explaining performance. The change should not be described as making every return perform an extra move or as eliminating every move. It simplifies which operation can be selected when initialization requires one. The actual construction behavior remains subject to the applicable elision rules.

That makes the facility valuable as a semantic simplification before any performance claim. A clearer rule can reduce implementation disagreement and make rejected source easier to explain without promising a particular instruction sequence.

The proposal also supplies a dedicated feature-test macro, __cpp_implicit_move. That lets source distinguish availability of the revised rule from a general language-version claim. A compatibility layer can therefore identify the semantic facility it depends on instead of assuming every compiler's C++23 mode has identical coverage.

Library support advances on its own schedule

GCC 13's library notes describe improved experimental C++20 support for the format header and std::format. They also list time-zone and clock work in chrono. These are standard-library facilities, so compiler parsing support alone does not establish their availability.

The C++23 library additions include monadic operations for std::expected, further constant-evaluation support, and new range adaptors and algorithms. The release's language and library lists answer different questions. A program can require both a particular frontend rule and a particular library implementation.

This is especially relevant to projects that use one compiler with another vendor's standard library. Selecting a C++23 language mode asks the frontend for language semantics. Headers and linked runtime components supply the library facilities used by the program. The source of those components needs to be part of an availability assessment.

The change notes' experimental qualification also belongs in that assessment. It communicates implementation status rather than creating a new standard edition. A library user should identify the concrete facility needed and the version that supplies it.

Diagnostics identify other move and lifetime mistakes

The release adds -Wself-move and -Wdangling-reference, and expands existing move-related warnings. These diagnostics concern source patterns that remain possible even as the language rules improve. They complement the implicit-move change without being the same mechanism.

A self-move warning identifies an operation applied to the same value as source and destination. A dangling-reference warning concerns a reference bound to a temporary whose lifetime has ended. The compiler's ability to diagnose a pattern does not imply a general proof that every other lifetime relationship is valid.

GCC also adds C++ excess-precision support, with different defaults in strict and GNU modes. That is another reminder that a language-mode flag can affect semantics beyond accepting new syntax. A build's selected mode is part of the program's behavior.

The practical effect of simpler implicit move is bounded and useful. Eligible return expressions become xvalues, and the former lvalue fallback no longer supplies a second interpretation. Code that needs another interpretation must express it explicitly, while library availability and diagnostic coverage remain separate parts of adopting GCC 13.