C++98 containers defined two kinds of iterator, ::iterator
s and ::const_iterators
. Generally, like this:
struct vec{
iterator be
STL containers are designed to be used in conjunction with STL algorithms. The generic algorithms currently found in the STL deal with iterators, but your template function transport_first
:
template<class Container, class T = Container::value_type>
void transport_first(Container&& c, std::vector<T>& v){
v.emplace_back(*std::forward<Container>(c).begin());
}
consists of container-based code, i.e.: it operates on containers rather than on iterators. We may be able to find such container-based algorithms as part of the STL as of C++20 thanks to concepts.
The "STL-like approach" to a generic transport_first
algorithm would actually be:
template<typename InIt, typename OutIt>
void transport_first(InIt src, OutIt dst) {
*dst = *src;
}
Following this approach (i.e.: iterators instead of containers), when it comes to generic code, whether to use move_iterator
or not can be simply determined at the "topmost" generic algorithm, since the passed iterators are copied further (i.e.: being passed by value into the "underlying" algorithms).
In addition, unlike the iterator-based transport_first
above, the STL is full of iterator pair-based algorithms (e.g.: std::copy
) to implement ranges. This interface makes overloading those container member functions on rvalues little attractive, because as already stated in this other answer, those overloaded member functions will be called, among others, on unnamed (non-const
) container objects, and unnamed container objects are limited to be used in a single expression, hindering the creation of an iterator pair by calling both begin()
and end()
member functions on the same container object.
What would really make sense for containers, is to have fresh new member functions for returning the corresponding move_iterator
objects:
move_iterator Container<T>::mbegin();
move_iterator Container<T>::mend();
This would be just a shortcut for calling std::make_move_iterator
with the value returned by the member functions returning a iterator
. The member function mbegin()
would be used in the iterator-based transport_first
as:
transport_first(coll.mbegin(), std::back_inserter(vec));
The corresponding function templates std::mbegin()
and std::mend()
would also make sense:
transport_first(std::mbegin(coll), std::back_inserter(vec));
You can trivially convert any non-const iterator into a move iterator. It makes generally no difference to the container.
You cannot trivially convert non-const iterators to const iterators. For example, a copy-on-write string (std::string
in the past for some compilers, custom strings could still be this) has to pessimistically detach from shared data when a non-const iterator is taken (with begin()
) in order to fulfill typical invalidation guarantees, which is hugely inefficient when you just want a const iterator.
In addition, C++ overloading rules don't allow you to introduce an rvalue overload of begin()
without changing the unspecified version to lvalue, and that would be a breaking change.
Finally, overloading begin()
on rvalues is not useful anyway - the expectation is that rvalue functions are called on rvalues, and except for those produced by std::move
, these rvalues are 1) going away soon (which would invalidate the obtained iterator) and 2) have no name, which means that they can only be used in one expression, which means you cannot call both begin()
and end()
to obtain an iterator pair, and a single iterator is useless, since you can never know whether it's safe to dereference it.