Search code examples
c++stdstd-rangesc++23

Why does std::ranges::find_if return an iterator but std::ranges::find_last_if does not?


Reading through cppreference.com, I noticed ranges::find_last_if does not return an iterator but ranges::find_if does. I am wondering if there is a good reason for this decision?

Apparently, the correct usage is to use ranges::find_if with a reversed range:

const auto it = std::ranges::find_if(data | std::views::reverse, func);

That said, ranges::find_last_if returning an iterator seems more intuitive to me so I am curious as to its purpose.


Solution

  • Basically, the std::ranges algorithms return the end iterator whenever they can.

    For example,

    • std::ranges::for_each returns a in_fun_result. The in component is an iterator to the end of the source range.
    • std::ranges::copy returns a in_out_result. in is an iterator to the end of the source range, and out is an iterator to the end of the destination range. (std::copy only returns the end of the destination range.)
    • std::ranges::fill and std::ranges::generate return an iterator to the end of the destination range. (The std algorithms return nothing.)

    The reason is that, unlike the traditional std algorithms, the ranges ones take an iterator and a sentinel. The only thing you can do with a sentinel (that is not itself an iterator) is to compare it with an iterator. If they compare equal, it means the end of range is reached.

    Since a sentinel is less useful that an iterator, and these algorithms need to get an end iterator anyway, they return that iterator to not lose valuable information.

    For std::ranges::find_last_if, this means it should return an iterator to the element it finds, and an end iterator. They naturally form a subrange, and that's what std::ranges::find_last_if actually returns.

    If you don't need the end iterator, you can extract the first iterator using subrange::begin.

    const auto it = std::ranges::find_last_if(data, func).begin();