繁体   English   中英

如何使用结构化绑定复制类型为 T& 的元素的类似元组的 object?

[英]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)));
...

其中语法暗示您可以将[a, b, c, ...]替换为诸如e之类的变量名称,然后abc的类型遵循一个奇怪的推导规则。

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

这是一个穿着新衣服的很老的C++疣:

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

代理是第二个 class 公民。 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),我的意思是,如果类似元组的绑定是引用,那么它们现在与 class 案例中的结构化绑定不同(除非我们这样做不同并损害位域的特性)。 我们现在在结构化绑定的工作方式上有一个非常微妙的不一致。

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

通过(3),我的意思是,如果我们写auto [...] =... ,我们期望一个副本,而不是 N 个副本。 在提供的示例中,几乎没有区别,因为复制聚合与复制每个成员相同,但这不是内在属性。 成员可以使用聚合来共享一些常见的 state,否则他们需要拥有自己的副本。 拥有不止一个复制操作可能会令人惊讶。

通过(4),我的意思是,您可以通过说“它们的工作方式就像您将[...]替换为 object 名称并且绑定名称是该事物部分的新名称”来教某人结构化绑定。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM