如何使用结构化绑定来复制类型为 T& 的元素的类似元组的对象?
How can I use a structured binding to copy a tuple-like object with elements whose type is T&?
这个问题的根源是我正在设计一个由std::vector
实现的二维容器。 operator[]
的结果类型是一个元素数量固定的代理class,然后我想和这个代理class使用结构化绑定,就像std::array
一样。这是一个简单的例子:
template<size_t stride>
struct Reference{
Container2D<stride>* container;
size_t index;
template<size_t I>
decltype(auto) get(){
return container->data()[I + index * stride];
}
};
/* the object means `stride` elements in container, starting at `index * stride` */
template<size_t stride>
struct Container2D{
std::vector<int>& data();
/* implemented by std::vector, simplify the template argument T */
Reference operator[](size_t index);
/* operator[] just constructs an object of Reference */
/* so it returns a rvalue */
};
namespace std{
template<size_t stride>
struct tuple_size<Reference<stride>>{
static constexpr size_t value = stride;
};
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* first: tuple_element_t<...> = T */
typedef int type;
};
}
在这种情况下,我试过:
Container2D<2> container;
/* init... */
auto [a, b] = container[0];
/* get a copy of each element */
auto& [c, d] = container[0];
/* compile error */
但是编译器说 “对 'Reference<...>' 类型的非常量左值引用不能绑定到 'Reference<...>' 类型的临时值”
所以如果我想通过结构化绑定修改元素,我必须:
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* second: tuple_element_t<...> = T& */
typedef int& type;
};
然后:
Container2D<2> container;
/* init... */
auto [a, b] = container[0];
/* get a reference to each element */
// auto& [c, d] = container[0];
/* still compile error, but who cares? */
但是在这种情况下,如果我想得到一个副本,我必须声明一些变量来复制这些引用变量。 这完全不是我想要的。有没有更好的方法可以轻松正确地处理这两种情况?
以下是本题的补充:
我知道结构化绑定的实现是:
"auto" [const] [volatile] [&/&&] "[" <vars> "]" "=" <expression>
并且可以实现为(在类似元组的情况下,简化一些边缘情况):
auto [const] [volatile] [&/&&] e = <expression>;
std::tuple_element_t<0, std::remove_reference_t<decltype(e)>> var_0(get<0>(std::forward(e)));
std::tuple_element_t<1, std::remove_reference_t<decltype(e)>> var_1(get<1>(std::forward(e)));
...
其中语法暗示你可以用一些变量名如e
替换[a, b, c, ...]
,然后是a
、b
和[=23的类型=]遵循一个奇怪的推导规则。
然而,这个匿名变量总是不是我们想要的,但是a
、b
和c
会是。那么为什么不保证a
、b
和c
的类型呢?对于 a
、b
和 c
,它可以仅将 cv-qualifier 和 ref-operator 应用于 std::tuple_element_t<I, E>
,使用 auto&& e
和 std::forward(e)
对于表达式,其他的和以前一样处理。
这是一个穿着新衣服的非常古老的C++疣:
std::vector<bool> x;
auto& rx = x[0]; // does not compile
代理人是第二 class 公民。它与 operator[]
的 return 不兼容,并使用 auto&
.
的结构化绑定来绑定它
没有解决方案 trade-offs。
为了让 auto&
绑定工作 as-is,必须有一些活的东西可以 operator[]
可以 return 引用(例如作为容器成员)。当被 auto&
和 auto
绑定时,那个东西的行为必须不同(例如,当复制时,它进入“复制”模式)。应该可以做到这一点,并使这个 exact 用法有效,但它将无法维护。
更合理的做法是放弃auto&
绑定。在这种情况下,您可以提供以 value-like 和 reference-like 方式运行的代理,例如像这样:
auto [a, b] = container[0]; // copy
auto [a, b] = container[0].ref(); // reference-like
为了完成这项工作,operator[]
return 是一个代理,get()
将为其 return 复制,并在其上调用 .ref()
return 是 get()
return 引用的代理。
问题的补充本身就很有趣。此语言功能中存在一些有趣的张力。我不在委员会中,但我可以列举一些朝这个方向倾斜的动机:(1)一致性(2)不同的演绎语义,(3)效率,(4)可教性和(5)生活
请注意,问题中的添加掩盖了一个重要的区别。绑定名称不是引用,而是别名。它们是所指事物的新名称。这是一个重要的区别,因为位域使用结构化绑定,但无法形成对它们的引用。
通过 (1),我的意思是,如果 tuple-like 绑定是引用,它们现在不同于 class 情况下的结构化绑定(除非我们以不同的方式做并妥协位域)。我们现在在结构化绑定的工作方式上存在非常微妙的不一致。
通过 (2),我的意思是,在语言的任何地方,auto&&
都发生一种类型推导。如果 auto&& [...]
翻译成绑定名称为 auto&&
的版本,则有 N 个不同的推导,可能有不同的 lvalue/rvalue-ness。这使它们比现在更复杂(相当复杂)
(3),我的意思是,如果我们写 auto [...] = ...
,我们期望一个副本,而不是 N 个副本。在提供的示例中,差别不大,因为复制聚合与复制每个成员相同,但这不是内在的 属性。成员可以使用聚合来共享一些公共状态,否则他们将需要拥有自己的副本。拥有多个复制操作可能会令人惊讶。
(4),我的意思是,你可以通过说“它们的工作方式就像你用对象名称替换 [...]
并且绑定名称是部分的新名称来教某人结构化绑定那个东西。
这个问题的根源是我正在设计一个由std::vector
实现的二维容器。 operator[]
的结果类型是一个元素数量固定的代理class,然后我想和这个代理class使用结构化绑定,就像std::array
一样。这是一个简单的例子:
template<size_t stride>
struct Reference{
Container2D<stride>* container;
size_t index;
template<size_t I>
decltype(auto) get(){
return container->data()[I + index * stride];
}
};
/* the object means `stride` elements in container, starting at `index * stride` */
template<size_t stride>
struct Container2D{
std::vector<int>& data();
/* implemented by std::vector, simplify the template argument T */
Reference operator[](size_t index);
/* operator[] just constructs an object of Reference */
/* so it returns a rvalue */
};
namespace std{
template<size_t stride>
struct tuple_size<Reference<stride>>{
static constexpr size_t value = stride;
};
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* first: tuple_element_t<...> = T */
typedef int type;
};
}
在这种情况下,我试过:
Container2D<2> container;
/* init... */
auto [a, b] = container[0];
/* get a copy of each element */
auto& [c, d] = container[0];
/* compile error */
但是编译器说 “对 'Reference<...>' 类型的非常量左值引用不能绑定到 'Reference<...>' 类型的临时值”
所以如果我想通过结构化绑定修改元素,我必须:
template<size_t stride>
struct tuple_element<Reference<stride>>{
/* 2 choices: */
/* second: tuple_element_t<...> = T& */
typedef int& type;
};
然后:
Container2D<2> container;
/* init... */
auto [a, b] = container[0];
/* get a reference to each element */
// auto& [c, d] = container[0];
/* still compile error, but who cares? */
但是在这种情况下,如果我想得到一个副本,我必须声明一些变量来复制这些引用变量。 这完全不是我想要的。有没有更好的方法可以轻松正确地处理这两种情况?
以下是本题的补充:
我知道结构化绑定的实现是:
"auto" [const] [volatile] [&/&&] "[" <vars> "]" "=" <expression>
并且可以实现为(在类似元组的情况下,简化一些边缘情况):
auto [const] [volatile] [&/&&] e = <expression>;
std::tuple_element_t<0, std::remove_reference_t<decltype(e)>> var_0(get<0>(std::forward(e)));
std::tuple_element_t<1, std::remove_reference_t<decltype(e)>> var_1(get<1>(std::forward(e)));
...
其中语法暗示你可以用一些变量名如e
替换[a, b, c, ...]
,然后是a
、b
和[=23的类型=]遵循一个奇怪的推导规则。
然而,这个匿名变量总是不是我们想要的,但是a
、b
和c
会是。那么为什么不保证a
、b
和c
的类型呢?对于 a
、b
和 c
,它可以仅将 cv-qualifier 和 ref-operator 应用于 std::tuple_element_t<I, E>
,使用 auto&& e
和 std::forward(e)
对于表达式,其他的和以前一样处理。
这是一个穿着新衣服的非常古老的C++疣:
std::vector<bool> x;
auto& rx = x[0]; // does not compile
代理人是第二 class 公民。它与 operator[]
的 return 不兼容,并使用 auto&
.
没有解决方案 trade-offs。
为了让 auto&
绑定工作 as-is,必须有一些活的东西可以 operator[]
可以 return 引用(例如作为容器成员)。当被 auto&
和 auto
绑定时,那个东西的行为必须不同(例如,当复制时,它进入“复制”模式)。应该可以做到这一点,并使这个 exact 用法有效,但它将无法维护。
更合理的做法是放弃auto&
绑定。在这种情况下,您可以提供以 value-like 和 reference-like 方式运行的代理,例如像这样:
auto [a, b] = container[0]; // copy
auto [a, b] = container[0].ref(); // reference-like
为了完成这项工作,operator[]
return 是一个代理,get()
将为其 return 复制,并在其上调用 .ref()
return 是 get()
return 引用的代理。
问题的补充本身就很有趣。此语言功能中存在一些有趣的张力。我不在委员会中,但我可以列举一些朝这个方向倾斜的动机:(1)一致性(2)不同的演绎语义,(3)效率,(4)可教性和(5)生活
请注意,问题中的添加掩盖了一个重要的区别。绑定名称不是引用,而是别名。它们是所指事物的新名称。这是一个重要的区别,因为位域使用结构化绑定,但无法形成对它们的引用。
通过 (1),我的意思是,如果 tuple-like 绑定是引用,它们现在不同于 class 情况下的结构化绑定(除非我们以不同的方式做并妥协位域)。我们现在在结构化绑定的工作方式上存在非常微妙的不一致。
通过 (2),我的意思是,在语言的任何地方,auto&&
都发生一种类型推导。如果 auto&& [...]
翻译成绑定名称为 auto&&
的版本,则有 N 个不同的推导,可能有不同的 lvalue/rvalue-ness。这使它们比现在更复杂(相当复杂)
(3),我的意思是,如果我们写 auto [...] = ...
,我们期望一个副本,而不是 N 个副本。在提供的示例中,差别不大,因为复制聚合与复制每个成员相同,但这不是内在的 属性。成员可以使用聚合来共享一些公共状态,否则他们将需要拥有自己的副本。拥有多个复制操作可能会令人惊讶。
(4),我的意思是,你可以通过说“它们的工作方式就像你用对象名称替换 [...]
并且绑定名称是部分的新名称来教某人结构化绑定那个东西。