【问题标题】:Do structured bindings and forwarding references mix well?结构化绑定和转发引用混合得好吗?
【发布时间】:2018-09-18 14:20:00
【问题描述】:

我知道我能做到

auto&& bla = something();

根据something 的返回值的constness,我会得到bla 的不同类型。

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

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

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

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

#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&amp;&amp; 正在完成它的工作,就会出现我所期望的错误:

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&amp;&amp;const 扣除部分(或模板-T&amp;&amp;) )。

【问题讨论】:

  • 因为需要考虑三种情况(基于something 的返回类型,您碰巧省略了)。像这个问题所要求的那样,详细说明所有这些问题,会使这变得有点宽泛。
  • 如果它有名字,它就不是一个右值引用 AFAIR。
  • @RichardHodges 从未说过任何关于右值引用的事情。我更关心 const 与非 const 左值参考案例。
  • “通用引用”(现在称为“转发引用”)仅出现在模板中。这些是右值引用。完全不同的东西。令人困惑的是,它们在不同的上下文中都用 && 表示。
  • 你试过了吗? - 回答你的问题,当然,为什么不呢?

标签: c++ language-lawyer c++17 forwarding-reference structured-bindings


【解决方案1】:

是的。结构化绑定和转发引用混合得很好

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

否则,e 被定义为

attribute-specifier-seqoptdecl-specifier-seq ref-qualifieropte 初始化器 ;

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

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

带有标识符列表简单声明称为结构化绑定声明([dcl.struct.bind])。 decl-specifier-seq 应仅包含 type-specifier autocv-限定符。 initializer 的格式为“=assignment-expression”,格式为“{assignment-expression} ”,或“(assignment-expression)”形式,其中assignment-expression是数组或非联合类类型。

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

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

声明这个未命名的变量:

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

该行为源自[dcl.spec.auto](特别是here)。在那里,我们确实对 initializer 进行了推理:

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

autoU 替换,&amp;&amp; 继续存在。这是我们的转发参考。如果演绎失败(只有当 something()void 时它才会失败),我们的声明是错误的。如果成功,我们就获取推导出的U 并将我们的声明视为:

U&& e = something();

根据 something() 的值类别和类型,这使得 e 成为左值或右值引用,即 const 限定为 not。

其余的结构化绑定规则遵循 [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&amp;a 的行为方式)。需要记住的一点。

有两个地方我能想到不是这种情况。

trailing-return-type 声明中,您必须只使用 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&&

【讨论】:

  • 关于您对decltype 的警告:您可以反驳相反的观点,即如果没有当前规范,this static assertion to fire 会令人惊讶。 (但该参数不适用于数组。)
  • @LucDanton 这让我觉得是在乞求这个问题?为什么你会假设它应该成立,而不知道它成立?
  • 我假设 &amp;tup.val == &amp;a 持有的方式相同,即最终引用同一事物的两种不同方式。
  • @LucDanton 更重要的是,没有办法区分auto&amp;&amp; [a] = tup; auto&amp;&amp; [b] = std::move(tup);,因为decltypeab 提供了相同的类型。
  • @LucDanton &amp;tup.val == &amp;a 对我来说并不意味着 decltype(a) 必须是 int
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-08
相关资源
最近更新 更多