15

我知道我能做到

auto&& bla = something();

并且根据const的返回值的好坏something,我会为bla.

这是否也适用于结构化绑定情况,例如

auto&& [bla, blabla] = something();

我猜是这样(结构化绑定搭载在auto初始化程序上,其行为如下),但我找不到明确的肯定。

更新:初步测试似乎符合我的预期(const正确推导):

#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;
}

现场演示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;

我仍然想确切地知道这种行为是在哪里指定的。

注意:使用“转发引用”来表示这种行为可能会延长它,但我没有一个好名字来给出const扣除部分auto&&(或模板T&&-就此而言)。

4

1 回答 1

8

是的。结构化绑定和转发引用混合得很好†</sup>。

一般来说,任何可以使用的地方‡</sup> auto,都可以auto&&用来获得不同的含义。特别是对于结构化绑定,这来自[dcl.struct.bind]

否则,e定义为

属性说明符序列opt decl 说明符序列 ref 限定符opt e 初始值设定项 ;

其中声明永远不会被解释为函数声明,并且声明的除declarator-id之外的部分取自相应的结构化绑定声明。

[dcl.dcl]中对这些部分有进一步的限制:

带有标识符列表简单声明称为结构化绑定声明 ([dcl.struct.bind])。decl -specifier-seq应该只包含type-specifiercv -qualifiers。初始值设定项的形式应为“<code>= assignment-expression ”、“<code>{ assignment-expression ”或“<code>( assignment-expression ”形式,其中assignment-expression为数组或非联合类类型。 auto } )

把它放在一起,我们可以分解你的例子:

auto&& [bla, blabla] = something();

声明这个未命名的变量:

auto               && e = something();
~~~~               ~~     ~~~~~~~~~~~
decl-specifier-seq        initializer
                   ref-qualifier

该行为源自[dcl.spec.auto](特别是此处)。在那里,我们确实对初始化器进行了扣除:

template <typename U> void f(U&& );
f(something());

auto替换为U, 和&&结转的地方。这是我们的转发参考。如果演绎失败(只有当something()是时它才会失败void),我们的声明是错误的。如果它成功了,我们就抓住推导的U并将我们的声明视为:

U&& e = something();

e根据值类别和类型,它会生成一个左值或右值引用,即 const 限定为 not something()

其余的结构化绑定规则遵循 [dcl.struct.bind],基于 的基础类型e、是否something()为左值以及是否e为左值引用。


†</sup> 有一个警告。对于结构化绑定,decltype(e)始终是引用的类型,而不是您可能期望的类型。例如:

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!

我传递 mytuple是一个左值,您希望将其底层元素作为左值引用传递,但它们实际上被转发了。这是因为decltype(a)is just int(被引用的类型),而不是int&(有意义的a行为方式)。要记住的事情。

‡</sup> 有两个地方我可以想到不是这种情况。

尾随返回类型声明中,您必须只使用auto. 你不能写,例如:

auto&& foo() -> decltype(...);

我能想到的唯一其他可能不是这种情况的地方是概念 TS 的一部分,您可以auto在更多地方使用它来推断/约束类型。在那里,当您推断的类型不是引用类型时使用转发引用将是不正确的,我认为:

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&&
于 2018-04-09T12:17:08.160 回答