结构化绑定和转发引用是否组合良好?

rub*_*nvb 15 c++ language-lawyer forwarding-reference c++17 structured-bindings

我知道我能做到

auto&& bla = something();
Run Code Online (Sandbox Code Playgroud)

并且根据const返回值的something不同,我会得到一个不同的类型bla.

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

auto&& [bla, blabla] = something();
Run Code Online (Sandbox Code Playgroud)

我猜是这样的(结构化绑定捎带在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;
}
Run Code Online (Sandbox Code Playgroud)

现场演示,如果我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;
Run Code Online (Sandbox Code Playgroud)

我仍然想知道这个行为的确切位置.

注意:使用"转发引用"来表示这种行为可能会延伸它,但是我没有一个好名字来给出const演绎部分auto&&(或模板T&&).

Bar*_*rry 8

是.结构化绑定和转发参考混合良好.

在一般情况下,任何地方您可以使用auto,你可以使用auto&&来获取不同的含义.具体来说,对于结构化绑定,这来自[dcl.struct.bind]:

否则,e定义为-if by

attribute-specifier-seq opt decl-specifier-seq ref-qualifier opt e initializer ;

声明永远不会被解释为函数声明,声明的声明部分以外的声明部分取自相应的结构化绑定声明.

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

简单声明与一个标识符列表被称为结构化绑定声明([dcl.struct.bind]).该DECL说明符-SEQ应当只包含类型说明符 auto品种 -qualifiers.的初始值设定应为形式"的= 分配表达 ",形式为" { 赋值表达式 } ",或形式为" ( 赋值表达式 ) ",其中赋值表达式是阵列或不愈合类型的.

把它放在一起,我们可以打破你的榜样:

auto&& [bla, blabla] = something();
Run Code Online (Sandbox Code Playgroud)

声明这个未命名的变量:

auto               && e = something();
~~~~               ~~     ~~~~~~~~~~~
decl-specifier-seq        initializer
                   ref-qualifier
Run Code Online (Sandbox Code Playgroud)

行为是从[dcl.spec.auto](具体在这里)派生的.在那里,我们确实对初始化器进行了推论:

template <typename U> void f(U&& );
f(something());
Run Code Online (Sandbox Code Playgroud)

其中auto被取代U,并且&&携带了过来.这是我们的转发参考.如果扣除失败(只有在扣除的情况something()void),我们的声明就是格式错误.如果成功,我们抓住推断出来U并将我们的声明视为:

U&& e = something();
Run Code Online (Sandbox Code Playgroud)

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

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


有一点需要注意.对于结构化绑定,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!
Run Code Online (Sandbox Code Playgroud)

我传递的tuple是一个左值,你希望它作为左值引用传递它的底层元素,但它们实际上是转发的.这是因为decltype(a)它只是int(引用的类型),而不是int&(a行为的有意义的方式).要记住的事情.

我可以想到两个地方不是这样的.

trailing-return-type声明中,您必须使用just auto.你不能写,例如:

auto&& foo() -> decltype(...);
Run Code Online (Sandbox Code Playgroud)

我可以想到的另一个地方可能不是这种情况,它是Concepts 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&&
Run Code Online (Sandbox Code Playgroud)