Here is my (simplified) attempt to implement a ranges::min_element
version that would work for both lvalue and rvalue arguments:
#include <iterator>
#include <algorithm>
#include <type_traits>
#include <utility>
namespace better_std_ranges
{
template<typename Range>
constexpr auto min_element(Range& range)
{
using std::begin;
using std::end;
return std::min_element(begin(range), end(range));
}
template<typename Range>
constexpr auto min_element(Range&& range)
{
static_assert(!std::is_reference_v<Range>, "wrong overload chosen");
class _result_iterator_type // todo: inherit from some crtp base that will provide lacking operators depending on _underlying_iterator_type::iterator_category
{
using _underlying_iterator_type = std::decay_t<decltype(std::begin(std::declval<Range&>()))>;
public:
explicit constexpr _result_iterator_type(Range&& range) noexcept(std::is_nothrow_move_constructible_v<Range>)
: _underlying_range{std::move(range)}
, _underlying_iterator(::better_std_ranges::min_element(_underlying_range))
{
}
using difference_type = typename _underlying_iterator_type::difference_type;
using value_type = typename _underlying_iterator_type::value_type;
using pointer = typename _underlying_iterator_type::pointer;
using reference = typename _underlying_iterator_type::reference;
using iterator_category = typename _underlying_iterator_type::iterator_category;
constexpr decltype(auto) operator*() const
{
return *_underlying_iterator;
}
// todo: define other member functions that were not provided by the inheritance above
private:
Range _underlying_range;
_underlying_iterator_type _underlying_iterator;
};
return _result_iterator_type{std::move(range)};
}
}
#include <vector>
#include <iostream>
auto make_vector()
{
return std::vector{100, 200, 42, 500, 1000};
}
int main()
{
auto lvalue_vector = make_vector();
auto lvalue_vector_min_element_iterator = better_std_ranges::min_element(lvalue_vector);
std::cout << *lvalue_vector_min_element_iterator << '\n';
auto rvalue_vector_min_element_iterator = better_std_ranges::min_element(make_vector());
std::cout << *rvalue_vector_min_element_iterator << '\n';
}
The output is
42
42
Surely it lacks some implementation details, but the idea must be clear: if an input range is an rvalue, the return value can store a moved copy of it. Thus it must be totally possible for std::ranges
algorithms to work with rvalue arguments.
My question is: why does the standard go the opposite way and just prohibit using rvalue ranges with its algorithms by introducing that strange std::ranges::dangling
placeholder?
There are two problems with this approach.
First, it breaks the semantics of the algorithm. The point of min_element
(and any other algorithm that returns an iterator) is to return an iterator into the range. You're not doing that - you're returning an iterator into a different range. That really confuses the notion of what the return even means in this case. What would you even compare this iterator to? There's no corresponding .end()
?
Second, the iterator model in C++ is based very strongly around the notion that iterators are cheap to copy. Every algorithm takes iterators by value and copies them around freely. Iterators are assumed to be light-weight and, importantly, non-owning. For forward iterators, copies of an iterator are assumed to be interchangeable.
Everything about this breaks if you suddenly have an iterator that has member std::vector<T>
that it refers into. Copying iterators becomes very expensive. And now each distinct iterator copy is actually an iterator into a completely different range?
You can do a little bit better by having the iterator have a member std::shared_ptr<std::vector<T>>
instead of a std::vector<T>
. This way copies are much cheaper and no longer independent, so you have something closer to a legitimate iterator. But now you have to do an extra allocation (to create the shared pointer), you still have the issue where the iterator you're returning is into a different range than the algorithm was given, and you have the issue where the algorithm has very different semantics based on whether you provide an lvalue or rvalue range.
Basically, min_element
on an rvalue range needs to either:
dangling<I>
could still let you get at the underlying I
)I don't think there's another option here, really.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.