c++ - 如何使用结构化绑定(bind)来复制类型为 T& 的元素的类似元组的对象?

标签 c++ c++17 structured-bindings

这个问题的根源是我正在设计一个由 std::vector 实现的二维容器。 . operator[] 的结果类型是一个代理类,有固定数量的元素,然后我想对这个代理类使用结构化绑定(bind),就像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<...>’类型的非常量左值引用不能绑定(bind)到‘Reference<...>’类型的临时值”

所以如果我想通过结构化绑定(bind)修改元素,我必须:

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? */

但在这种情况下,如果我想得到一个拷贝,我必须声明一些变量来复制这些引用变量。 这完全不是我想要的。有没有更好的方法可以轻松正确地处理这两种情况?

以下是对这个问题的补充:

我知道结构化绑定(bind)的实现是:

"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 的类型, bc遵循一个奇怪的推论规则。

然而,这个匿名变量始终不是我们想要的,而是a , bc将。那么为什么不确保 a 的类型呢? , bc ?它可以将 cv-qualifier 和 ref-operator 应用于 std::tuple_element_t<I, E>对于 a , bc , 使用 auto&& estd::forward(e)对于表达式,其他的和以前一样处理。

最佳答案

这是一个穿着新衣服的非常古老的 C++ 疣:

std::vector<bool> x;
auto& rx = x[0]; // does not compile

代理人是二等公民。从 operator[] 按值返回并使用结构化绑定(bind)与 auto& 绑定(bind)它是不兼容的。

没有不取舍的解决方案。

要让 auto& 绑定(bind)按原样工作,必须有一些事件的东西可以让 operator[] 返回一个引用(例如作为容器成员)。当 auto&auto 绑定(bind)时,该对象的行为必须不同(例如,复制时,它会进入“复制”模式)。应该可以做到这一点,并使这种精确用法有效,但它将无法维护。

更合理的做法是放弃auto&绑定(bind)。在这种情况下,您可以提供以类值和类引用方式运行的代理,例如像这样:

auto [a, b] = container[0]; // copy
auto [a, b] = container[0].ref(); // reference-like

为了完成这项工作,operator[] 返回一个代理,get() 将为其返回拷贝,并调用 .ref()它返回一个代理,get() 返回引用。

问题的补充本身就很有趣。此语言功能中存在一些有趣的张力。我不在委员会中,但我可以列举一些朝这个方向倾斜的动机:(1) 一致性 (2) 不同的演绎语义,(3) 效率,(4) 可教性和 (5) 生活

请注意,问题中的添加掩盖了一个重要的区别。绑定(bind)名称不是引用,而是别名。它们是所指事物的新名称。这是一个重要的区别,因为位域使用结构化绑定(bind),但无法形成对它们的引用。

对于 (1),我的意思是,如果类似元组的绑定(bind)是引用,那么它们现在不同于类案例中的结构化绑定(bind)(除非我们以不同的方式进行处理并妥协位域的功能)。我们现在在结构化绑定(bind)的工作方式上存在非常微妙的不一致。

对于 (2),我的意思是,在语言中的任何地方,auto&& 都会发生一种类型推导。如果 auto&& [...] 翻译成绑定(bind)名称为 auto&& 的版本,则有 N 种不同的推导,可能具有不同的左值/右值。这使得它们比现在更复杂(这相当复杂)

对于 (3),我的意思是,如果我们编写 auto [...] = ...,我们期望一个拷贝,而不是 N 个拷贝。在提供的示例中,差别不大,因为复制聚合与复制每个成员相同,但这不是固有属性。成员可以使用聚合来共享一些公共(public)状态,否则他们将需要拥有自己的拷贝。拥有多个复制操作可能会令人惊讶。

对于 (4),我的意思是,您最初可以通过说“它们的工作方式就像您将 [...] 替换为对象名称并且绑定(bind)名称是新的一样来教某人结构化绑定(bind)那东西的各个部分的名称”。

关于c++ - 如何使用结构化绑定(bind)来复制类型为 T& 的元素的类似元组的对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63396197/

相关文章:

c++ - 来自 std::tuple 的引用绑定(bind)类型

c++ - Lambda 隐式捕获因从结构化绑定(bind)声明的变量而失败

c++ - 在发送之前访问 QNetworkRequest 数据

c++ - QRect 中的 QMouseEvent 问题

c++ - std::array<const T, n> 与 std::array<T, n> 重载解析

c++ - 模板化的 constexpr 变量

c++ - 如何在C++中使用<文件系统>访问相对父目录

c++ - 为什么结构化绑定(bind)依赖于 tuple_element?

c++ - 如何在没有内存操作的情况下在 C++ 和 STL 中定义二维数组?

c++ - 类方法作为函数参数