Abstract
This PR introduces ranges::base standalone function. It provides machinery for retrieving iterator of desired type from iterator base() chains (base().base().base()....).
Motivation
auto list = vec | view::transform(&Data::i) | view::take_exactly(3);
list.begin().base().base() != vec.begin(); // types mismatchYou have to "guess" count of base() calls to the desired layer of iterator adaptor.
auto list = vec | view::transform(&Data::i) | view::take_exactly(3);
base<iterator_t<Vec>>(list.begin()) == vec.begin();Usage with algorithms:
auto list = vec | view::transform(&Data::i) | view::take_exactly(3);
iterator i = base<iterator>(find(list, 10));Interface
// iterator
ranges::base(iter); // iter.base()
ranges::base<3>(iter); // iter.base().base().base()
ranges::base<iterator_t<Range>>(iter); // iter.base()...
ranges::base<Range>(iter); // try ranges::base<iterator_t<Range>>(iter), then sentinel_t, then hard error
?? ranges::base(iter, range); // ranges::base<decltype(range)>(iter)
// range (just for symetry)
ranges::base(range); // range.base()
ranges::base<3>(range); // range.base().base().base()
ranges::base<List>(range); // range.base()...
// range iterator_wise (for algorithms which returns range)
ranges::base(ranges::iterator_wise, range); // iterator_range(ranges::base(range.begin()), ranges::base(range.end()))
ranges::base<3>(ranges::iterator_wise, range); // iterator_range(ranges::base<3>(range.begin()), ranges::base<3>(range.end()))
ranges::base<List>(ranges::iterator_wise, range); // iterator_range(ranges::base<iterator_t<List>>(range.begin()), ranges::base<sentinel_t<List>>(range.end()))
Principle of operation
Iterator/range base() called until it's type does not match required. On type comparison references removed, constness remains.
WIP
Activity
8I don't see enough of a need
The main motivation for this - algorithms:
- There are things, that close to impossible to do with projections.
// Data has following structure // | Header (16 bytes) | Payload(128byte) | .... // Seeking payload with specific header. using Iterator = iterator_t< std::vector<char> >; std::optional<Iterator> lookup( const std::vector<char>& raw_data, const std::vector<char>& required_header) { auto chunks = raw_data | view::sliding(16+128); auto headers = chunks | view::forward_range | view::transform([](auto&& rng){ return rng | view::take(16); }); auto founded = find_if(headers, [&](auto&& rng){ return equal(rng, required_header); }); if (founded == headers.end()) return {}; return {base<Iterator>(founded) + 16}; // return Itrerator pointing at payload start } - You already have a view chain that do transformation , and you don't want to repeat yourself.
Events = std::list<IEvent>; Events events; struct SyncPoint : IEvent{ SyncFence& sync_fence(); const SyncFence& sync_fence() const; }; struct SyncFence{ std::string text; } // This part already exists and in use. // ------------- template<class T> auto get_only = view::transform([](IEvent& e){ return dynamic_cast<T*>(&e); }) | view::remove(nullptr) | view::indirect; auto get_sync_fences = get_only<SyncPoint> | view::transform([](SyncPoint& sync_point) -> SyncFence& { return sync_point.sync_fence(); }); // ------------- // with base<> auto sync_fences = events | get_sync_fences ; auto founded = base<Events>(find(sync_fences, "red", &SyncFence::text)); // with projection // It does not look much longer, but all that transformation chain // has substantial cognitive burden. While writing that transformer - // you'll forgot what exactly you wanted to search. auto founded = find_if(events, [](const IEvent& e) -> bool { const SyncPoint* p = dynamic_cast<const SyncPoint*>(&e); if (!p) return false; const SyncFence& fence = p->sync_fence(); return (fence.text == "red"); }); - You expose container adapter, not container itself:
struct { auto elements(){ return m_data | view::indirect; } void emplace_element(elements_iterator before, Element& data){ auto data_iterator = base<Elements>(before); m_data.emplace(data_iterator, data); } private: using Elements= std::vector<Element*>; Elements m_elements; }
I never want to call .base() an arbitrary number of times.
The core part of this PR is only:
ranges::base<Iterator>(iter);Everything else actually is auxiliary, and can be hided/removed.
added example 3.
This PR is now badly out of date. I never was able to convince myself that this problem justifies any solution, or that this solution is it. Do you still want this, @tower120?
My last line of defense is "ranges::base = better projections for lookup algorithms".
That's probably main use-case for ranges::base(). Previously I thought that ranges::base() can be used inside mutable algorithms/actions (like actions::remove( rng1, view::not_unique)), but abandoned that idea.
Important property of projection is that it can't skip elements, nor change their order. While view can.
using Pair =std::pair<int, int>;
std::vector<Pair> pairs;
auto iter1 = ranges::find(pairs | view::partition(256) | view::each_range(view::take(4)).base(pairs);You can't do that at all with projections, and you can't do that efficiently with predicates. And you definitely can't have that elegant solution with manual loop.
N.B.
auto each_range = [](auto view){
// view::forward_iter = view::iota(view.begin(), view.end()) kinda...
return view::forward_iter | view::transform([](auto&& rng){ return *rng | view; });
};
// view::partition = given a range and N value, return range of ranges:
// N 2
// In 1 2 3 4 5 6 7 8 9
// Out 12|34|56|78|9I still think this is useful. But if you think it has too narrow application, close it.
UPDATE 1. Example updated, I mistakenly used wrong view.
I'm not a fan of this functionality. I don't see enough of a need, and I never want to call
.base()an arbitrary number of times.