I believe that Clang is correct.
TL;DR: some lvalues can be implicitly moved, but a structured binding is not such a lvalue.
- The name of a structured binding is an lvalue:
[dcl.struct.bind]/1:
A structured binding declaration introduces the identifiers v0
, v1
, v2
,… of the identifier-list as names of structured bindings.
[dcl.struct.bind]/4:
Each vi
is the name of an lvalue of type Ti
that refers to the object bound to ri
; the referenced type is ri
.
- An variable name (which is normally an lvalue) can be moved in a
return
statement if it names an implicitly movable entity:
An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type.
In the following copy-initialization contexts, a move operation is first considered before attempting a copy operation:
- If the expression in a
return
([stmt.return]) or co_return
([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
- [...]
- As can be seen in the definition of implicitly movable entity, only objects and (rvalue) references can be implicitly moved. But a structured binding is neither.
[basic.pre]/3:
An entity is a value, object, reference, [or] structured binding[...].
So I believe that a structured binding cannot be implicitly moved.
If y
were an object or reference, then it would be implicitly movable in return y;
.
Edit: C++17 as written specified that structured bindings to tuple members are references. This was corrected by CWG 2313.