Why are flat_set and flat_map not ContiguousRange?

Viewed 171

Is it done on purpose(lack of data() member function)? It prevents decaying from flat_set/flat_map to std::span which can be useful if we treate flat_set/flat_map as sorted vector.

Is it safe to make sth like that?

template<typename T>
std::span<const T> make_span(const std::flat_set<T>& fset)
{
    return std::span<const T>{fset.empty() ? nullptr : std::addressof(*fset.begin()),
                              fset.size()};
}
1 Answers

std::flat_set is supposed to be a container adaptor (like std::queue or std::stack), and shall specifically work with container backends that support random access iteration, like std::vector and std::deque do. From P1222 (the std::flat_set proposal):

Any sequence container supporting random access iteration can be used to instantiate flat_set. In partic-ular, vector and deque can be used.

No requirements are imposed w.r.t. any contiguous nature of the storage, and std::deque indeed doesn't have a data() function, nor does it store its elements in a contiguous range. This means:

  1. Yes, it is done on purpose. As std::flat_set is supposed to be an adaptor, access to a possibly underlying contiguous range can be delegated to the backend, and the requirements for std::flat_set can be more flexible.

  2. You make_span template should be safe as it only accepts std::flat_set instances with the default underlying container, std::vector. I think it's a bit dangerous though - when compilation fails for a std::flat_set<T, std::deque<T>>, someone might be tempted to add the second template parameter std::flat_set<T, Container<T>> and run into UB when accessing elements beyond the first underlying std::deque bucket.

Also note that std::flat_map in its current form uses two separate containers for key and value storage to improve cache locality while searching for a matching key. Even if std::flat_map required a contiguous backend, this can't go along with a meaningful std::flat_map::data() access function.

Related