Shall structured binding be returned from a function as rvalue in C++20?
Asked Answered
A

1

26

Consider a C++20 program where in function foo there is a structured binding auto [y]. The function returns y, which is converted in object of type A. A can be constructed either from const reference of from rvalue-reference.

#include <tuple>
#include <iostream>

struct A {
    A(const int &) { std::cout << "A(const int &) "; }
    A(int &&) { std::cout << "A(int &&) "; }
};

A foo() {
    auto [y] = std::make_tuple(1);
    return y;
}

int main() { foo(); }

Which one of the constructors shall be selected according to C++20 language standard?

Clang selects A(const int &) and GCC selects A(int &&), demo: https://gcc.godbolt.org/z/5q779vE6T

Does one of the compilers not support yet the standard in that respect?

Anticipatory answered 26/8, 2021 at 15:32 Comment(13)
Seems to be unrelated to structured bindings, doesn't it?Concentrated
@IgorR. How so? The compilers behave differently (to each other) when using structured binding, and behave the same when using a variable. The question is which compiler behaves correctly in this example using structured binding.Mebane
It is related to which constructor of A is selected no to the binding. Rule of thumb, these kind of constructors should be explicit. These implicit type converting constructors can be nastyLexi
@IgorR. I'd say unrelated to function returnMixie
Clang is right because an id-expression denoting a structured binding is an lvalue.Mixie
@LanguageLawyer If we instead write int y = 1; return y;, then clang uses int&& constructor. Is id-expression denoting a variable not an lvalue (or are both compilers wrong in that case, or is there some rule that applies to that case but does not apply to structured bindings, or something else)?Mebane
@Mebane In your example y is an int, so it qualifies for rvalue conversion on return. In the op's example y is actually a reference to the sub-object of some unnamed object the structured binding declared. That stops the move if it is an lvalue reference. I still can't figure out which one is correct though :(Goldman
@Mebane An id-expression denoting a variable is an lvalue, but when it is an operand of a return statement, in some cases it is treated as rvalue (first). The rule is well-known.Mixie
@Goldman Ah, so the core issue is that structured binding is a reference even when auto& is not used.Mebane
@LanguageLawyer The rule may be well known, but I'm not sure how well it is known how that rule applies to structured bindings. If GCC is wrong, then I suspect that it may be related to their implementation of that rule (but that's just a guess).Mebane
@Mebane Yes. All of the names in the [] are actually references into the object that the structured binding "captured". The reference qualification on structured binding applies to that captured object, not the bindings.Goldman
If we use A a = y; then both compilers correctly use the lvalue reference constructor, which strengthens my guess that it's that particular rule that GCC (wrongly) applies.Mebane
@Mebane [dcl.struct.bind]/3/4 changed between C++17 and C++20, see wg21.cmeerw.net/cwg/issue2313. C++17 calls structured bindings «variables» there.Mixie
I
6

I believe that Clang is correct.

TL;DR: some lvalues can be implicitly moved, but a structured binding is not such a lvalue.

  1. 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.

  1. 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
  • [...]
  1. 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.

Ignite answered 26/8, 2021 at 16:4 Comment(9)
Regarding 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. I'm not sure that is correct. An rvalue reference is also an lvalue, and per this the bindings can be rvalue references if I'm reading it correctly.Goldman
also, stating that y is not an object is incorrect IMO.Floccule
@Goldman By that logic, you may as well argue that there's no reference, only object or function, because every reference names an object or function. I'm not sure if this is self-contained.Ignite
The binding just makes y an int. The return statement is returning an A, witch is being created thru conversion constructor. The binding should not interfere in the way return works.Greathouse
@Floccule I also feel wrong, but this is language-lawyer.Ignite
it is an object, but it is not of class type.Floccule
@Goldman An rvalue reference is also an lvalue The first is type, the second is expression. Your statement doesn't make sense.Mixie
Although when one does "return {y}"; both become const int& gcc.godbolt.org/z/3nxYno8nnGreathouse
Thanks, I reported GCC bug: gcc.gnu.org/bugzilla/show_bug.cgi?id=102116Anticipatory

© 2022 - 2024 — McMap. All rights reserved.