我明白你的意思。
我确实认为这是一个真正的问题。
我的答案是,社区必须确切地同意移动嵌套对象(例如容器)的含义。
无论如何,这需要容器实现者的合作。
而且,就标准集装箱而言,规格良好。
我对标准容器可以更改为“概括”“移动”的含义感到悲观,但这不能阻止新的用户定义的容器利用移动惯用语。
问题是据我所知,没有人深入研究过这个问题。
就像现在一样,std::move
似乎暗示“浅”移动(顶部“价值类型”的一级移动)。
从某种意义上说,您可以移动整个事物,但不一定可以移动单个部分。
反过来,这使得尝试“std::move”非拥有范围或提供指针/迭代器稳定性的范围变得毫无用处。
一些图书馆,例如相关std::ranges
简单地拒绝参考范围的右值,我认为这只是踢罐头。
假设你有一个容器Bag
。
什么应该std::move(bag)[0]
and std::move(bag).begin()
返回?返回什么实际上是由容器的实现决定的。
很难想到通用的数据结构,如果数据结构很简单(例如动态数组),那么为了与以下内容保持一致struct
s (std::move(s).field
) std::move(bag)[0]
应该是一样的std::move(bag[0])
然而,该标准已经强烈反对我的观点:https://en.cppreference.com/w/cpp/container/vector/operator_at https://en.cppreference.com/w/cpp/container/vector/operator_at现在改变可能为时已晚。
同样适用于std::move(bag).begin()
根据我的逻辑,它应该返回一个move_iterator
(或类似的东西)。
为了让事情变得更糟,std::array<T, N>
按照我的预期工作(std::move(arr[0])
相当于std::move(arr)[0]
)。
然而std::move(arr).begin()
是一个简单的指针,因此它丢失了“转发/移动”信息!这是一团糟。
所以,是的,要回答你的问题,你可以检查是否using Type = decltype(*std::forward<Bag>(bag).begin());
是一个右值,但通常不会作为右值实现。
也就是说,你必须抱有最好的希望并相信.begin
and *
以非常具体的方式实施。
通过(以某种方式)检查范围本身的类别,您可以更好地了解情况。
也就是说,目前您只能使用自己的设备:如果您知道bag
绑定到一个 r 值,并且该类型在概念上是一个“拥有”值,您当前必须使用以下命令std::make_move_iterator
.
我目前正在用我拥有的自定义容器进行很多实验。https://gitlab.com/correaa/boost-multi https://gitlab.com/correaa/boost-multi然而,通过尝试允许这一点,我打破了标准容器在移动方面的预期行为。
此外,一旦您处于非拥有范围的领域,您就必须使迭代器可以“手动”移动。
我发现从经验上来说,区分顶级动作很有用(std::move
)和元素明智的移动(例如bag.mbegin()
or bag.moved().begin()
)。
否则我会发现自己超载std::move
如果有的话,这应该是最后的手段。
换句话说,在
template<class MyRange>
void f(MyRange&& r) {
std::copy(std::forward<MyRange>(r).begin(), ..., ...);
}
事实是r
绑定到右值并不一定意味着元素可以移动,因为MyRange
可以简单地是“刚刚”生成的较大容器的非拥有视图。
因此,一般来说,您需要一个外部机制来检测是否MyRange
是否拥有这些值,而不仅仅是检测“值类别”*std::forward<MyRange>(r).begin()
正如你所提议的。
我想通过范围,人们可以希望在未来用某种类似适配器的东西来指示深度移动”std::ranges::moved_range
" 或使用 3 参数std::move
.