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?

eerorika
  • 232,697
  • 12
  • 197
  • 326
Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 3
    Seems to be unrelated to structured bindings, doesn't it? – Igor R. Aug 26 '21 at 15:44
  • @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. – eerorika Aug 26 '21 at 15:48
  • 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 nasty – Pepijn Kramer Aug 26 '21 at 15:49
  • @IgorR. I'd say unrelated to function return – Language Lawyer Aug 26 '21 at 15:49
  • 2
    Clang is right because an id-expression denoting a structured binding is an lvalue. – Language Lawyer Aug 26 '21 at 15:53
  • @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)? – eerorika Aug 26 '21 at 15:56
  • 1
    @eerorika 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 :( – NathanOliver Aug 26 '21 at 15:58
  • @eerorika 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](https://timsong-cpp.github.io/cppwp/n4868/class.copy.elision#3) is well-known. – Language Lawyer Aug 26 '21 at 16:00
  • @NathanOliver Ah, so the core issue is that structured binding is a reference even when `auto&` is not used. – eerorika Aug 26 '21 at 16:00
  • @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). – eerorika Aug 26 '21 at 16:02
  • @eerorika 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. – NathanOliver Aug 26 '21 at 16:02
  • 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. – eerorika Aug 26 '21 at 16:09
  • @eerorika [dcl.struct.bind]/3/4 changed between C++17 and C++20, see https://wg21.cmeerw.net/cwg/issue2313. C++17 calls structured bindings «variables» there. – Language Lawyer Aug 26 '21 at 16:12

1 Answers1

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.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • 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](http://eel.is/c++draft/dcl.struct.bind#4) the bindings can be rvalue references if I'm reading it correctly. – NathanOliver Aug 26 '21 at 16:07
  • also, stating that `y` is not an object is incorrect IMO. – user1095108 Aug 26 '21 at 16:09
  • @NathanOliver 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. – cpplearner Aug 26 '21 at 16:12
  • 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. – Cleiton Santoia Silva Aug 26 '21 at 16:14
  • @user1095108 I also feel wrong, but this is [tag:language-lawyer]. – cpplearner Aug 26 '21 at 16:15
  • it **is** an object, but it is not of class type. – user1095108 Aug 26 '21 at 16:15
  • @NathanOliver _An rvalue reference is also an lvalue_ The first is type, the second is expression. Your statement doesn't make sense. – Language Lawyer Aug 26 '21 at 16:16
  • Although when one does "return {y}"; both become const int& https://gcc.godbolt.org/z/3nxYno8nn – Cleiton Santoia Silva Aug 26 '21 at 16:16
  • Thanks, I reported GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102116 – Fedor Sep 02 '21 at 07:05