为什么用唯一命名的变量来定义结构化绑定?

rub*_*nvb 4 c++ c++17 structured-bindings

为什么通过唯一命名的变量定义结构化绑定,而所有模糊的“名称都绑定到”语言呢?

我个人认为结构化绑定的工作方式如下。给定一个结构:

struct Bla
{
    int i;
    short& s;
    double* d;
} bla;
Run Code Online (Sandbox Code Playgroud)

下列:

cv-auto ref-operator [a, b, c] = bla;
Run Code Online (Sandbox Code Playgroud)

(大致)等于

cv-auto ref-operator a = bla.i;
cv-auto ref-operator b = bla.s;
cv-auto ref-operator c = bla.d;
Run Code Online (Sandbox Code Playgroud)

以及数组和元组的等效扩展。但是显然,这太简单了,并且所有这些模糊的特殊语言都用来描述需要发生的事情。

因此,我显然遗漏了一些东西,但是在某种意义上讲,如果可以说是一个折叠定义(在标准语言中更容易阅读)的定义明确的扩展,那究竟是什么情况呢?

似乎由结构化绑定定义的变量的所有其他行为实际上都遵循我认为将用于定义概念的简单扩展“规则”。

Nic*_*las 5

存在结构化绑定以允许一种语言的多个返回值,该语言不允许函数将多个值解析为一个以上的值(因此不会干扰C ++ ABI)。这意味着无论使用哪种语法,编译器最终都必须存储实际的返回值。因此,该语法需要一种方法来确切讨论您将如何存储该值。由于C ++在事物的存储方式(作为引用或值)方面具有一定的灵活性,因此结构化绑定语法需要提供相同的灵活性。

因此,“ auto &或” auto&&或“ auto选择”适用于主要值而不是子对象。

其次,我们不想使用此功能影响性能。这意味着引入的名称将永远不会是主对象的子对象的副本。它们必须是引用或实际的子对象本身。这样,人们就不必担心使用结构化绑定对性能的影响。它是纯语法糖。

第三,该系统旨在处理用户定义的对象以及所有公共成员的数组/结构。对于用户定义的对象,“名称绑定”到真正的语言参考(即调用的结果)get<I>(value)。如果你存储const auto&的对象,那么value将是一个const&到该对象,并get可能会返回const&

对于数组/公共结构,“名称绑定到” 不是引用的东西。就像您键入value[2]或一样对待它们value.member_namedecltype除非已解压缩的成员本身是引用,否则使用此类名称将不会返回引用。

通过这种方式,结构化绑定仍然是纯语法糖:它以最有效的方式访问该对象。对于用户定义的类型,get每个子对象仅调用一次,并存储对结果的引用。对于其他类型,它使用的名称类似于数组/成员选择器。


Bar*_*rry 5

似乎结构化绑定定义的变量的所有其他行为实际上都遵循我认为将用于定义概念的简单扩展“规则”。

它有点像。除了扩展不是基于右侧的表达式之外,它基于引入的变量。这实际上非常重要:

X foo() {
    /* a lot of really expensive work here */
   return {a, b, c};
}

auto&& [a, b, c] = foo();
Run Code Online (Sandbox Code Playgroud)

如果扩展为:

// note, this isn't actually auto&&, but for the purposes of this example, let's simplify
auto&& a = foo().a;
auto&& b = foo().b;
auto&& c = foo().c;
Run Code Online (Sandbox Code Playgroud)

它不仅效率极低,而且在许多情况下也可能是错误的。例如,想象一下如果foo()被实现为:

X foo() {
    X x;
    std::cin >> x.a >> x.b >> x.c;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

因此,它扩展为:

auto&& e = foo();
auto&& a = e.a;
auto&& b = e.b;
auto&& c = e.c;
Run Code Online (Sandbox Code Playgroud)

这确实是确保我们所有的绑定都来自同一个对象而没有任何额外开销的唯一方法。

以及数组和元组的等效扩展。但显然,这太简单了,而且所有这些模糊的特殊语言都用于描述需要发生的事情。

分三种情况:

  1. 数组。每个绑定的行为就好像它是对适当索引的访问。
  2. 类似元组。每个绑定都来自对 的调用std::get<I>
  3. 聚合状。每个绑定命名一个成员。

这还不算太糟糕?假设,#1 和 #2 可以组合(可以将元组机制添加到原始数组),但这样做可能更有效。

措辞中的大量复杂性 (IMO) 来自处理价值类别。但是,无论指定其他任何内容的方式如何,您都需要它。