区分rvalue视图和rvalue容器

3
我正在编写一些基于容器或可迭代对象的算法。基本上,我操作支持for( : )风格迭代的对象(我很少直接使用for( : ),而是遵循查找beginend迭代器的方式)。
std库中,大多数可迭代对象都是容器。它们拥有自己的数据,并且让您查看它们。
当算法通过右值引用接受容器时,这意味着容器的内容也可以被取走。例如,如果我编写了一个名为concatinate的函数,它接受两个vector并返回第三个,如果两个vector都被move了,我们将想要重用第一个vector,然后使用move_iterator从第二个vector中取出数据以提高效率。
然而,随着C++1y的string_view和类似概念的类型,我们有了不是容器而是指向容器的视图的可迭代对象。从语义上讲,我认为视图的行为类似于指针,因此它们的“按值”复制是视图进入容器的复制,而不是它们所引用的数据。如果我通过右值引用获取string_view风格的视图,则并不意味着它拥有内容:移动内容是不合理的,就像移动指针的内容一样,并不仅仅因为指针本身是一个右值。
同时,容器遵循值语义,因为它们是右值,移动它们的内容是有效的。
对于string_view,这不是一个问题,但我编写了更通用的view类,例如contiguous_range_view,它允许您在vectorarrayarr[]的子集上执行操作,就好像它是一个非可变大小的缓冲区。
这些视图并不总是将其内容视为const,但它们不拥有其内容。因此,视图是右值并不意味着它们的内容是右值!
我的算法(例如concatinate)在这里遇到了问题。视图与容器几乎无法区分,而右值容器可以被移动,而右值视图则不能。返回视图的函数是一个很好的模式,因为视图在语义上是指针类型。
我正在寻找一种简洁明了的方法来区分容器和视图。是否有计划通过某些属性或标签区分C++1y中的string_view,以便我可以模拟或挂钩?如果没有,是否有一个好的模式?
如果我成功阻止视图被意外移动,有时仍需要移动它们,因此我需要一种标记视图为可移动的方法,而不是作为右值引用。我怀疑一个 make_move_range 函数可以解决这个问题(它将接受一个可迭代范围,并对 beginend 应用 std::make_move_iterator)。

你如何将内容添加到视图中? - R. Martinho Fernandes
正如你所说,假设一个类rvalue具有返回迭代器的成员函数,那么这些迭代器将引用可移动的变量是一种错误的假设。为了实现这种跳跃,必须断言所有权关系。 - Andrew Tomazos
@R.MartinhoFernandes 我认为Yakk真正的意思是“连接”,而不是“追加”。 - Casey
@Casey 是的,澄清了语言。 - Yakk - Adam Nevraumont
3个回答

2
选项1:我认为“容器”和“视图”概念最显著的区别在于容器具有插入和删除操作,而视图没有。我会尝试利用这一事实编写一个类型特征来检测“容器”的性质。具体而言,我认为所有标准容器都有一个“void erase(iterator)”成员。
选项2:容器管理内存,视图不管理。所有标准容器都有一个“typedef allocator_type”的成员。
值得注意的是,这些观察结果对于“std::array”或内置数组都不适用。

一个特性类 is_owning_container,它寻找 allocator_type 并在 std::array<T,N>T[N] 上进行专门化,似乎工作得相当不错。更高级的版本将确保 allocator_type 至少看起来像一个分配器,并使用 void erase(iterator) 进行双重检查,或者只是对每个 std 容器进行专门化并完成它... - Yakk - Adam Nevraumont

1
如果目标是区分std::stringstd::string_view,我会使用类似以下的代码:
template <
class String = std::string, 
class = typename std::enable_if<!std::is_void<decltype(std::declval<String>().substr(0))>::value>::type // Check if the type has the substr member
>
constexpr bool is_view(const String& str = String())
{
    return std::is_nothrow_constructible<String, std::string>::value;
}

作为容器,应该管理内存,因此它们的构造函数不是nothrow,而容器视图似乎具有nothrow构造函数。
编辑: 感谢Casey的答案,我认为这种检测类型是否为视图或容器的元函数(假设视图不是平凡对象而数组是)会起作用。
template <class Type>
constexpr bool is_view()
{
    return (!std::is_trivial<Type>::value) 
        && (!std::is_constructible<Type, 
             std::allocator<typename Type::value_type>>::value);
}

如果Typestd:vector<int, MyAllocator<int>>,它无法从std::allocator<int>构造。我认为您正在过度复杂化应该是一个简单的SFINAE测试,即Type是否具有嵌套类型allocator_type - Casey

0

或许这个通用的模式会有所帮助:

struct Container
{
    CopyIterator begin() &;
    CopyIterator end() &;
    MoveIterator begin() &&;
    MoveIterator end() &&;
};

struct View
{
    CopyIterator begin();
    CopyIterator end();
};

这需要更改标准容器,这超出了我的范围。 :) - Yakk - Adam Nevraumont
@Yakk:你可以添加一个类型特征来识别一个类是容器还是视图,然后只在适当的情况下转移移动语义——实际上就像一个包装器一样实现上述操作。 - Andrew Tomazos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接