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

Why are structured bindings defined in terms of a uniquely named variable?

为什么结构化绑定是通过唯一命名的变量和所有模糊的 "name is bound to" 语言来定义的?

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

struct Bla
{
    int i;
    short& s;
    double* d;
} bla;

以下:

cv-auto ref-operator [a, b, c] = bla;

(大致)相当于

cv-auto ref-operator a = bla.i;
cv-auto ref-operator b = bla.s;
cv-auto ref-operator c = bla.d;

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

所以我显然遗漏了一些东西,但是在某种意义上定义明确的扩展的确切情况是什么,比方说,折叠表达式,这在标准语中更容易阅读?

似乎结构化绑定定义的变量的所有其他行为实际上都遵循好像简单的扩展"rule"我认为会被用来定义这个概念。

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

因此 auto &auto&&auto 选择应用于主值而不是子对象。

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

第三,系统旨在处理用户定义的对象和 arrays/structs 以及所有 public 成员。在用户定义对象的情况下,"name is bound to" 是一个真正的语言引用,是调用 get<I>(value) 的结果。如果您为对象存储 const auto&,那么 value 将是该对象的 const&,并且 get 可能 return 是 const& .

对于 arrays/public 结构,"names are bound to" 不是引用 的东西。它们的处理方式与您键入 value[2]value.member_name 完全相同。对此类名称执行 decltype 不会 return 引用,除非解压缩的成员本身就是引用。

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

It seems all the other behaviour of the variables defined by a structured binding actually follow the as-if simple expansion "rule" I'd think would be used to define the concept.

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

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

auto&& [a, b, c] = foo();

如果扩展为:

// 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;

它不仅效率极低,而且在很多情况下还可能主动出错。例如,假设 foo() 被实现为:

X foo() {
    X x;
    std::cin >> x.a >> x.b >> x.c;
    return x;
}

因此,它扩展为:

auto&& e = foo();
auto&& a = e.a;
auto&& b = e.b;
auto&& c = e.c;

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

And the equivalent expansions for arrays and tuples. But apparently, that would be too simple and there's all this vague special language used to describe what needs to happen.

分三种情况:

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

还不错吧?假设地,#1 和#2 可以组合(可以将元组机制添加到原始数组),但这样做可能更有效not

措辞 (IMO) 的相当一部分复杂性来自处理值类别。但是无论指定其他任何方式,您都需要它。