6

I'm trying to familiarize with the ranges-v3 library, that will be part of the C++20 standard. To do so, I'm trying to refactor some toy code by replacing (where suitable) classic iterators and algorithms with the new available constructs. In this particular example, I can't figure out how to pass the returned iterator of a call to ranges::min_element (which replaced a call to std::min_element) to another function of mine which accepts a classic iterator as parameter.

I've searched inside the documentation of the library looking for some sort of functions like smartIt2classicIt with no success.

Here is a minimal example

void f(std::vector<int>& v, std::vector<int>::iterator it); // old function that I want to reuse
auto predicate = [](int i){ return true; }; // check function

std::vector<int> v;
// auto min_el = std::min_element(...); // old code
auto filtered_range = v | ranges::view::filter(predicate); // to avoid a dangling iterator
auto min_el = ranges::min_element(filtered_range);

f(v, min_el); // pass min_el to f: doesn't compile with the new code

At first I expected that the result of ranges::min_element was implicitly convertible to a classic iterator, but I was wrong: the compiler returns a long error saying that cannot convert a ranges::basic_iterator bla bla bla to a std::vector bla bla bla iterator. Given this error I deduce that ranges::min_element indeed returns some sort of iterator, but how to use it the old way?

I see three possible solutions:

  1. Change the way min_el is passed to f
  2. Change the type of the second argument of f (possibly in a backward-compatible way)
  3. Change both things

but I can't figure out how to implement any of them. Maybe there is another solution? I also see another possible source of troubles, since the returned iterator probably refers to filtered_range instead of v... Any help is welcome!

Rackbox
  • 283
  • 1
  • 11
  • "*that will be part of the C++20 standard*" It won't be. Not in its entirety. You should instead pay attention to [what is actually in C++20](https://timsong-cpp.github.io/cppwp/ranges). – Nicol Bolas Aug 01 '19 at 13:52
  • 2
    @NicolBolas Your phrasing can be read in a very unkind way... – Max Langhof Aug 01 '19 at 13:53
  • 2
    Imagine you did something like `min_el = v | takeEverySecondElement()`. Incrementing `min_el` should of course advance two elements inside the vector, but `f` presumably expects the passed iterator to advance by only one element. Maybe what you really want is an index into the vector? Basically, try to mentally change individual pieces of the problem and see what you would expect and which requirements/conflicts it exposes (e.g. what if you had a `std::map` instead of a vector?) – Max Langhof Aug 01 '19 at 13:54
  • 1
    Note that `f` takes a copy of `v` (you pass it by value). Is that on purpose? That would mean that the iterator you pass to `f` points to a different vector than the one you have inside of `f`. – sebrockm Aug 01 '19 at 14:34
  • @sebrockm it's a typo, I'm editing the question. Thank you for your note! – Rackbox Aug 01 '19 at 14:38
  • @NicolBolas sorry for the error, feel free to edit my question to fix it! – Rackbox Aug 01 '19 at 14:50
  • In general, I would prefer option 2 and change the design of `f` to make it "iterator-agnostic" (and if possible also "container-agnostic"). Without knowing, what it is doing, it's hard to give some good advice, but making the iterator argument a template would be a good start. – sebrockm Aug 01 '19 at 15:03

1 Answers1

3

You can convert an adapted iterator to its base iterator via the base() member function.

For example:

std::vector<int>::const_iterator foo(std::vector<int> const& v) {
    auto filtered = v | ranges::view::filter([](int i){return i > 5;});
    auto min_el = ranges::min_element(filtered);
    return min_el.base();
}

Note that this only removes one layer of wrapping, it doesn't drop to the bottom. So if you had another adapter then you'd need another base():

std::vector<int>::const_iterator foo(std::vector<int> const& v) {
    auto filtered = v | ranges::view::filter([](int i){return i > 5;})
                      | ranges::view::transform([](int i){ return i * i; });
    auto min_el = ranges::min_element(filtered);
    return min_el.base().base();
}
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Great! Is there a way to make this in one line? I've tried to do directly `return ranges::min_element(v | ...).base();`, but now the returned value of `ranges::min_element` is of type `ranges::dangling` and has no `base()` member. Any possible workaround to this? – Rackbox Aug 01 '19 at 14:44
  • 1
    @Rackbox This is by design in an effort to making the potential for dangling iterators more apparent (this specific case would be safe, but the language doesn't have a good way of differentiating between safe and unsafe uses). – Barry Aug 01 '19 at 15:35