c++ - 如何使用结构化绑定来复制具有类型为 T& 的元素的类元组对象?
问题描述
这个问题的根源是我正在设计一个由std::vector
. 的结果类型operator[]
是一个具有固定数量元素的代理类,然后我想对这个代理类使用结构化绑定,就像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)));
...
其中语法暗示您可以将 替换为[a, b, c, ...]
一些变量名称,例如e
,然后是 的类型a
,b
并c
遵循一个奇怪的推论规则。
然而,这个匿名变量总是不是我们想要的,而是a
,b
并且c
将会是。那么为什么不确保a
,b
和的类型c
呢?它可以只将 cv-qualifier 和 ref-operator 应用于std::tuple_element_t<I, E>
for a
, b
and c
, 使用auto&& e
andstd::forward(e)
表达式,其他的和以前一样处理。
解决方案
这是一个穿着新衣服的非常古老的 C++ 疣:
std::vector<bool> x;
auto& rx = x[0]; // does not compile
代理人是二等公民。从值返回operator[]
并使用结构化绑定将其绑定是不兼容的auto&
。
没有权衡就没有解决方案。
为了让auto&
绑定按原样工作,必须有一些活着的东西operator[]
可以返回一个引用(例如作为容器成员)。那个东西在被绑定时必须表现得与被绑定不同auto&
(auto
例如,当被复制时,它进入“复制”模式)。应该可以做到这一点,并使这个确切的用法工作,但它将是不可维护的。
更合理的方法是放弃auto&
绑定。在这种情况下,您可以提供以类似值和类似引用的方式运行的代理,例如:
auto [a, b] = container[0]; // copy
auto [a, b] = container[0].ref(); // reference-like
为了使这项工作,operator[]
返回一个get()
将为其返回副本的代理,并调用.ref()
它返回一个为其get()
返回引用的代理。
这个问题的补充本身就很有趣。这种语言特征有一些有趣的张力。我不是委员会成员,但我可以说出一些倾向于这个方向的动机:(1)一致性(2)不同的演绎语义,(3)效率,(4)可教性,以及(5)生活
请注意,问题中的添加掩盖了一个重要的区别。绑定的名称不是引用,而是别名。它们是所指向事物的新名称。这是一个重要的区别,因为位域适用于结构化绑定,但无法形成对它们的引用。
通过(1),我的意思是,如果类似元组的绑定是引用,那么它们现在与类情况下的结构化绑定不同(除非我们以不同的方式这样做并损害位域上的特性)。我们现在在结构化绑定的工作方式上有一个非常微妙的不一致。
通过(2),我的意思是,在语言的任何地方,auto&&
都有一种类型的演绎发生。如果auto&& [...]
翻译成绑定名称的版本,auto&&
则有 N 种不同的扣除,可能具有不同的左值/右值。这使它们比现在更复杂(这非常复杂)
通过(3),我的意思是,如果我们写auto [...] = ...
,我们期望一个副本,而不是 N 个副本。在提供的示例中,几乎没有区别,因为复制聚合与复制每个成员相同,但这不是内在属性。成员可以使用聚合来共享一些共同的状态,否则他们需要拥有自己的副本。拥有不止一个复制操作可能会令人惊讶。
通过(4),我的意思是,您可以通过说“它们的工作方式就像您[...]
用对象名称替换并且绑定名称是该事物部分的新名称一样”来教某人结构化绑定。
推荐阅读
- python - 我正在尝试对散点图中的绘图圆圈进行颜色编码并参考图例。数据是通过 csv 文件
- python - Python 请求超过 dynatrace SAAS url 的最大重试次数
- database - 时间序列数据库“指标限制”?
- android - 如何在从 Play 商店安装的构建之上安装 Android 调试或发布构建并保留数据?
- sql - 如何在 Postgres 中使用带有数学运算的 order by 子句的聚合结果
- reactjs - 没有调用 React/Javascript 函数
- node.js - Puppeteer,与 node.js 交换缓冲区
- r - 基于最小频率创建一个新的数据框
- macos - 如何升级 websphere server stub 的 jre?
- symfony - Symfony 4 - LiipImagine - 如何清除缓存?