Search code examples
c++algorithmc++20std-ranges

How to fix invalid iterator error while using views pipeline and ranges adjacent_find in c++20


I'm trying to check if all values in nested vector are same/unique. I wrote this program (simplified, but same error):

godbolt link

#include <algorithm>
#include <iostream>
#include <ranges>
#include <vector>

auto main() -> int
{
    std::vector<std::vector<int>> vectors_of_ints {
        {1, 2, 3},
        {4, 5, 6}
    };

    auto get_vectors = [](const std::vector<int>& v) { return v; };
    auto get_ints = [](const int& i) { return i; };

    auto all_ints = vectors_of_ints
           | std::views::transform(get_vectors)
           | std::views::join
           | std::views::transform(get_ints);


    auto status = std::ranges::adjacent_find(all_ints, std::not_equal_to{}) != std::ranges::end(all_ints);

    return status;
}

but I'm getting this error:

<source>:22:44: error: no match for call to '(const std::ranges::__adjacent_find_fn) (std::ranges::transform_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int> > >, main()::<lambda(const std::vector<int>&)> > >, main()::<lambda(const int&)> >&, std::not_equal_to<void>)'
   22 |     std::cout << std::ranges::adjacent_find(all_ints, std::not_equal_to{}) != std::ranges::end(all_ints);
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[...] // too long to post
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/bits/ranges_base.h:594:13:   required for the satisfaction of 'forward_range<_Range>' [with _Range = std::ranges::transform_view<std::ranges::join_view<std::ranges::transform_view<std::ranges::ref_view<std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > > >, main::._anon_112> >, main::._anon_113>&]
/opt/compiler-explorer/gcc-13.2.0/include/c++/13.2.0/concepts:67:28: note:   'std::forward_iterator_tag' is not a base of 'std::input_iterator_tag'
   67 |     concept derived_from = __is_base_of(_Base, _Derived)
      |                            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 1

Which concludes with: std::forward_iterator_tag is not a base of std::input_iterator_tag.

I would like to understand the problem I'm having here and how to fix that.


Solution

  • std::ranges__adjacent_find requires a forward_range as input, but your all_ints is only an input_range. From cppreference:

    join_view models forward_range when:

    • ranges::range_reference_t<V> is a reference type, and
    • V and ranges::range_reference_t<V> each model forward_range.

    std::ranges::range_reference_t<Range> is the return type of an element of a range of type Range. Because you transform vector_of_ints with your get_vectors, an element of the resulting range is std::vector and thus std::ranges::range_reference_t is not of reference type (See demo ). Note this happens only because auto follows the template argument deduction rules and does not deduce reference. If get_vectors to return a reference to vector instead, you can use decltype(auto):

    auto get_vectors = [](const std::vector<int>& v) -> decltype(auto) { return v; };
    

    or specify the return type of the lambda explicitly.

    Or the simple

    auto all_ints = vectors_of_ints | std::views::join;
    

    works.

    If you really want to copy the contents, consider creating a new vector from all_ints:

    std::vector<int> all_ints_copy(all_ints.begin(), all_ints.end());
    

    In C++23 std::ranges::to simplifies this process and mitigates all copies to the end:

    auto all_ints_copy_to = vectors_of_ints
                        | std::views::join
                        | std::ranges::to<std::vector<int>>();
    

    See demo