为什么结构化绑定将变量作为值而不是引用引入?
Why does structured binding introduce variables as values, not references?
我正在学习结构化绑定声明。我的理解是,在 auto& [x, y] = expr;
变量 x
和 y
中引入了 "reference to std::tuple_element<i, E>::type
" 类型(因为 i=0, 1
和 E
是不可见的类型变量 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
.
我正在学习结构化绑定声明。我的理解是,在 auto& [x, y] = expr;
变量 x
和 y
中引入了 "reference to std::tuple_element<i, E>::type
" 类型(因为 i=0, 1
和 E
是不可见的类型变量 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 expressionstd::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-idget
is looked up in the scope ofE
by class member access lookup, and if that finds at least one declaration, the initializer ise.get<i>()
. Otherwise, the initializer isget<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 entitye
is an lvalue reference and an xvalue otherwise. Given the typeTi
designated bystd::tuple_element<i, E>::type
, eachvi
is a variable of type “reference toTi
” 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 isTi
.