Question

I am writing some container or iterable-based algorithms. Basically, I operate on objects that support for( : ) style iteration (I rarely use for( : ) directly, but follow how it looks up the begin and end iterators).

In the std library, most every iterable objects are containers. They both own their data, and they let you look at it.

When an algorithm takes a container by rvalue reference, this implies that the contents of the container are also free to be taken from. As an example, if I write concatinate which takes two vectors and returns a third, if both vectors are moved, we will want to reuse the first vector then use move_iterators to take the data out of the second vector for efficiencies sake.

However, with C++1y's string_view and types that are similar in conception, we have iterable objects that are not containers, but rather views into containers. Semantically, I believe views behave like pointers, so their "by-value" duplication is duplication of the view into the container, not the data they refer to. If I take a string_view style view by rvalue reference, that doesn't mean it owns the contents: moving from the contents is not justified, no more than moving the contents of a pointer just because the pointer itself is an rvalue.

Meanwhile, containers follow value semantics, and moving their contents because they are an rvalue is valid.

For string_view this isn't a problem, but I have written more generic view classes, such as contiguous_range_view, which lets you act on subsets of a vector or array or arr[] as if it was a non-mutable sized buffer.

These views do not always treat their contents as const, but they do not own their contents. So the view being an rvalue does not imply that their contents are rvalues!

My algorithms like concatinate run into problems here. Views are nearly indistinguishable from containers, and rvalue containers are valid to be moved-from, while rvalue views are not. And a function that returns a view by value is a good pattern, as views are semantically pointer-types.

I'm looking for a nice, clean way to distinguish between containers and views. Is there a plan to distinguish string_view in C++1y through some property or tag that I could emulate or hook into now? If not, is there a good pattern?

If I manage to block views from being moved-from accidentally, I will still need to be able to move from them sometimes, so I'll need a way to mark a view as being a move-from candidate other than being an rvalue reference. I suspect a make_move_range function might solve that problem (that takes an iterable range, and applies std::make_move_iterator to begin and end).

Was it helpful?

Solution

Option 1: I think the most visible difference between Container and View concepts is that containers have insert and erase operations, and views don't. I'd try to use that fact to write a type trait to detect Container-ness. Specifically, I think all standard containers have an void erase(iterator) member.

Option 2: Containers manage memory, views do not. Standard containers all have a member typedef allocator_type.

Notably, neither of these observations holds true for std::array or builtin arrays.

OTHER TIPS

If the goal is to distinguish between std::string and std::string_view, I would use something like this :

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;
}

As containers, should manage memory, their constructors are not nothrow, whereas containers view seem to have nothrow constructors.

EDIT: Thanks to the Casey answer I think that this kind of metafunction to detect whether a type is a view or a container (assuming that views are not trivial objects but arrays are) would work.

template <class Type>
constexpr bool is_view()
{
    return (!std::is_trivial<Type>::value) 
        && (!std::is_constructible<Type, 
             std::allocator<typename Type::value_type>>::value);
}

Maybe this general pattern would help:

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

struct View
{
    CopyIterator begin();
    CopyIterator end();
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top