Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ranges::base #1179

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

ranges::base #1179

wants to merge 3 commits into from

Conversation

tower120
Copy link
Contributor

@tower120 tower120 commented May 6, 2019

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 mismatch

You 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

@ericniebler
Copy link
Owner

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.

@tower120
Copy link
Contributor Author

tower120 commented May 12, 2019

I don't see enough of a need

The main motivation for this - algorithms:

  1. 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
    }
  2. 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");
    });
  3. 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.

@tower120
Copy link
Contributor Author

tower120 commented Jun 6, 2019

added example 3.

@ericniebler
Copy link
Owner

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?

@tower120
Copy link
Contributor Author

tower120 commented Dec 4, 2019

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|9

I 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants