为什么结构化绑定将变量作为值而不是引用引入?

Why does structured binding introduce variables as values, not references?

我正在学习结构化绑定声明。我的理解是,在 auto& [x, y] = expr; 变量 xy 中引入了 "reference to std::tuple_element<i, E>::type" 类型(因为 i=0, 1E 是不可见的类型变量 e)。此外,这些变量初始化为 get<i>(e).

所以,如果我使用 auto&get<> returns 一个值(不是引用),它不应该编译,因为你不能将左值绑定到临时值。但是,以下示例在某些版本的 GCC、Clang 和 Visual Studio:

中为我构建
#include <cstddef>
#include <tuple>
#include <type_traits>

struct Foo {
    template<std::size_t i>
    int get() { return 123; }
};

namespace std {
    template<> struct tuple_size<Foo> : integral_constant<size_t, 1> {};
    template<std::size_t i> struct tuple_element<i, Foo> { using type = int; };
}

int main() {
    Foo f;
    auto& [x] = f;
    x++;
}

此外,C++ Insights 清楚地表明 clang 将结构化绑定扩展为:

Foo f = Foo();
Foo & __f17 = f;
std::tuple_element<0, Foo>::type x = __f17.get<0>();
x++;

在这里,它声明 x 不是引用,而是值。这是为什么?

我期望左值引用和编译错误:e(上例中的__f17)是一个左值引用。

那是因为auto&不适用于结构化绑定。它应用于引用结构的基础实体。在您的 cppinsights 片段中,那将是 __f17.

如果您改用 auto [x],该代码段将展开成这样

Foo f = Foo();
Foo __f17 = f; // Difference here
std::tuple_element<0, Foo>::type x = __f17.get<0>();
x++;

绑定本身始终是对底层对象的一种引用。然而,cppinsights 代码并不能准确地表示这一点。 C++标准中的相关段落是这样说的

[dcl.struct.bind]

3 Otherwise, if the qualified-id std​::​tuple_­size<E> names a complete type, the expression std​::​tuple_­size<E>​::​value shall be a well-formed integral constant expression and the number of elements in the identifier-list shall be equal to the value of that expression. The unqualified-id get is looked up in the scope of E by class member access lookup, and if that finds at least one declaration, the initializer is e.get<i>(). Otherwise, the initializer is get<i>(e), where get is looked up in the associated namespaces. In either case, get<i> is interpreted as a template-id. [ Note: Ordinary unqualified lookup is not performed.  — end note ] In either case, e is an lvalue if the type of the entity e is an lvalue reference and an xvalue otherwise. Given the type Ti designated by std​::​tuple_­element<i, E>​::​type, each vi is a variable of type “reference to Ti” initialized with the initializer, where the reference is an lvalue reference if the initializer is an lvalue and an rvalue reference otherwise; the referenced type is Ti.