结构化绑定和转发引用混合得好吗?
Do structured bindings and forwarding references mix well?
我知道我能做到
auto&& bla = something();
并且根据 something
的 return 值的 const
ness,我会得到 bla
.
的不同类型
这是否也适用于结构化绑定情况,例如
auto&& [bla, blabla] = something();
我猜是这样(结构化绑定搭载在 auto
初始值设定项上,其行为是这样的),但我找不到明确的答案。
更新: 初步测试似乎符合我的预期(正确推导 const
ness):
#include <tuple>
using thing = std::tuple<char, int*, short&, const double, const float&>;
int main()
{
char c = 0;
int i = 1;
short s = 2;
double d = 3.;
float f = 4.f;
thing t{c, &i, s, d, f};
auto&& [cc, ii, ss, dd, ff] = t;
c = 10;
*ii = 11;
ss = 12;
dd = 13.;
ff = 14.f;
}
Live demo,如果 auto&&
正在执行它的工作,就会出现我预期的错误:
main.cpp: In function 'int main()':
main.cpp:20:10: error: assignment of read-only reference 'dd'
dd = 13.;
^~~
main.cpp:21:10: error: assignment of read-only reference 'ff'
ff = 14.f;
我仍然想确切地知道指定此行为的位置。
注意:使用“转发引用”表示此行为可能会延伸它,但我没有一个好名字来给出 auto&&
的 const
推导部分(或模板- T&&
就此而言)。
是的。结构化绑定和转发引用混合得很好†.
一般来说,任何地方‡都可以用auto
,可以用auto&&
来获得不同的意思。特别是对于结构化绑定,这来自 [dcl.struct.bind]:
Otherwise, e
is defined as-if by
attribute-specifier-seqopt decl-specifier-seq ref-qualifieropt e
initializer ;
where the declaration is never interpreted as a function declaration and the parts of the declaration other than the declarator-id are taken from the corresponding structured binding declaration.
[dcl.dcl]中对这些部分有进一步的限制:
A simple-declaration with an identifier-list is called a structured binding declaration ([dcl.struct.bind]). The decl-specifier-seq shall contain only the type-specifier auto
and cv-qualifiers. The initializer shall be of the form “=
assignment-expression”, of the form “{
assignment-expression }
”, or of the form “(
assignment-expression )
”, where the assignment-expression is of array or non-union class type.
把它放在一起,我们可以分解你的例子:
auto&& [bla, blabla] = something();
如声明此未命名变量:
auto && e = something();
~~~~ ~~ ~~~~~~~~~~~
decl-specifier-seq initializer
ref-qualifier
该行为源自 [dcl.spec.auto] (specifically here)。在那里,我们确实对 initializer:
进行了推导
template <typename U> void f(U&& );
f(something());
其中 auto
被 U
替换,&&
继续。这是我们的转发参考。如果演绎失败(只有 something()
是 void
时才会失败),我们的声明是 ill-formed。如果成功,我们获取推导的 U
并将我们的声明视为:
U&& e = something();
这使得 e
成为左值或右值引用,即 const 不合格,基于 something()
的值类别和类型。
其余结构化绑定规则遵循 [dcl.struct.bind],基于 e
的基础类型,something()
是否为左值,以及是否为左值e
是左值引用。
† 有一个警告。对于结构化绑定,decltype(e)
始终是 referenced type,而不是您可能期望的类型。例如:
template <typename F, typename Tuple>
void apply1(F&& f, Tuple&& tuple) {
auto&& [a] = std::forward<Tuple>(tuple);
std::forward<F>(f)(std::forward<decltype(a)>(a));
}
void foo(int&&);
std::tuple<int> t(42);
apply1(foo, t); // this works!
我传递我的 tuple
是一个左值,您希望将其底层元素作为左值引用传递,但它们实际上被转发了。这是因为 decltype(a)
只是 int
(引用类型),而不是 int&
(a
行为的有意义方式)。要记住的事情。
‡ 我能想到有两个地方不是这样。
在 trailing-return-type 声明中,您必须只使用 auto
。你不能写,例如:
auto&& foo() -> decltype(...);
我能想到的唯一一个可能不是这种情况的地方是 Concepts TS 的一部分,您可以在更多地方使用 auto
到 deduce/constrain 类型。在那里,当您推导的类型不是引用类型时使用转发引用将是 ill-formed 我认为:
std::vector<int> foo();
std::vector<auto> a = foo(); // ok, a is a vector<int>
std::vector<auto&&> b = foo(); // error, int doesn't match auto&&
我知道我能做到
auto&& bla = something();
并且根据 something
的 return 值的 const
ness,我会得到 bla
.
这是否也适用于结构化绑定情况,例如
auto&& [bla, blabla] = something();
我猜是这样(结构化绑定搭载在 auto
初始值设定项上,其行为是这样的),但我找不到明确的答案。
更新: 初步测试似乎符合我的预期(正确推导 const
ness):
#include <tuple>
using thing = std::tuple<char, int*, short&, const double, const float&>;
int main()
{
char c = 0;
int i = 1;
short s = 2;
double d = 3.;
float f = 4.f;
thing t{c, &i, s, d, f};
auto&& [cc, ii, ss, dd, ff] = t;
c = 10;
*ii = 11;
ss = 12;
dd = 13.;
ff = 14.f;
}
Live demo,如果 auto&&
正在执行它的工作,就会出现我预期的错误:
main.cpp: In function 'int main()':
main.cpp:20:10: error: assignment of read-only reference 'dd'
dd = 13.;
^~~
main.cpp:21:10: error: assignment of read-only reference 'ff'
ff = 14.f;
我仍然想确切地知道指定此行为的位置。
注意:使用“转发引用”表示此行为可能会延伸它,但我没有一个好名字来给出 auto&&
的 const
推导部分(或模板- T&&
就此而言)。
是的。结构化绑定和转发引用混合得很好†.
一般来说,任何地方‡都可以用auto
,可以用auto&&
来获得不同的意思。特别是对于结构化绑定,这来自 [dcl.struct.bind]:
Otherwise,
e
is defined as-if byattribute-specifier-seqopt decl-specifier-seq ref-qualifieropt
e
initializer;
where the declaration is never interpreted as a function declaration and the parts of the declaration other than the declarator-id are taken from the corresponding structured binding declaration.
[dcl.dcl]中对这些部分有进一步的限制:
A simple-declaration with an identifier-list is called a structured binding declaration ([dcl.struct.bind]). The decl-specifier-seq shall contain only the type-specifier
auto
and cv-qualifiers. The initializer shall be of the form “=
assignment-expression”, of the form “{
assignment-expression}
”, or of the form “(
assignment-expression)
”, where the assignment-expression is of array or non-union class type.
把它放在一起,我们可以分解你的例子:
auto&& [bla, blabla] = something();
如声明此未命名变量:
auto && e = something();
~~~~ ~~ ~~~~~~~~~~~
decl-specifier-seq initializer
ref-qualifier
该行为源自 [dcl.spec.auto] (specifically here)。在那里,我们确实对 initializer:
进行了推导template <typename U> void f(U&& );
f(something());
其中 auto
被 U
替换,&&
继续。这是我们的转发参考。如果演绎失败(只有 something()
是 void
时才会失败),我们的声明是 ill-formed。如果成功,我们获取推导的 U
并将我们的声明视为:
U&& e = something();
这使得 e
成为左值或右值引用,即 const 不合格,基于 something()
的值类别和类型。
其余结构化绑定规则遵循 [dcl.struct.bind],基于 e
的基础类型,something()
是否为左值,以及是否为左值e
是左值引用。
† 有一个警告。对于结构化绑定,decltype(e)
始终是 referenced type,而不是您可能期望的类型。例如:
template <typename F, typename Tuple>
void apply1(F&& f, Tuple&& tuple) {
auto&& [a] = std::forward<Tuple>(tuple);
std::forward<F>(f)(std::forward<decltype(a)>(a));
}
void foo(int&&);
std::tuple<int> t(42);
apply1(foo, t); // this works!
我传递我的 tuple
是一个左值,您希望将其底层元素作为左值引用传递,但它们实际上被转发了。这是因为 decltype(a)
只是 int
(引用类型),而不是 int&
(a
行为的有意义方式)。要记住的事情。
‡ 我能想到有两个地方不是这样。
在 trailing-return-type 声明中,您必须只使用 auto
。你不能写,例如:
auto&& foo() -> decltype(...);
我能想到的唯一一个可能不是这种情况的地方是 Concepts TS 的一部分,您可以在更多地方使用 auto
到 deduce/constrain 类型。在那里,当您推导的类型不是引用类型时使用转发引用将是 ill-formed 我认为:
std::vector<int> foo();
std::vector<auto> a = foo(); // ok, a is a vector<int>
std::vector<auto&&> b = foo(); // error, int doesn't match auto&&