Skip to content

operator== of a random access any_view does not detect end of range #92

Description

@jiixyj

Describe the deficiency

operator== of the iterator type does not take cache_or_index into account when comparing with a sentinel. When has_index is true, advancing an iterator using operator+= just increments cache_or_index. But then in operator==, only dispatch<sentinel_compare_t>(poly); is called, ignoring cache_or_index.

To Reproduce

I have no simple reproducer yet as I just encountered this bug. It should be as easy as iterating over a random access any_view, maybe having sanitizers enabled.

edit: Here is a reproducer on Compiler Explorer: https://godbolt.org/z/GfsEWW5rE

#include <iostream>
#include <vector>

#include <beman/any_view/any_view.hpp>

namespace bav = beman::any_view;
using opt = bav::any_view_options;

int main() {
    auto v1 = std::vector<std::tuple<int, int>>{{1, 1}, {2, 2}, {3, 3}} |
              std::views::transform(
                  [](const auto& el) -> std::tuple<const int&, const int&> {
                      return el;
                  });

    bav::any_view<std::tuple<int, int>, opt::random_access,
                  std::tuple<const int&, const int&>>
        v = std::move(v1);

    for (auto [i1, i2] : v) {
        std::cerr << i1 << ' ' << i2 << '\n';
    }

    std::cerr << "end\n";
}

Expected Behavior

Iteration over a random access any_view should reliably stop.

Additional Discussions

To solve this, maybe there should be a sentinel_compare_at_t capability? operator== could look like this then:

    [[nodiscard]] constexpr bool operator==(std::default_sentinel_t) const {
        // sentinel comparison must dispatch for a contiguous iterator
        if constexpr (has_cache and not contiguous) {
            return cache_or_index == cache_type{};
        } else if constexpr (has_index) {
            return dispatch<sentinel_compare_at_t<DiffT>>(poly, cache_or_index);
        } else {
            return dispatch<sentinel_compare_t>(poly);
        }
    }

...and the new capability:

struct sentinel_compare_t : unary_protocol {
    template <not_adaptor T>
    static bool fn(const T& self);

    template <adaptor IteratorAdaptorT>
    [[nodiscard]] static constexpr bool fn(const IteratorAdaptorT& adaptor) {
        return adaptor.iterator == adaptor.sentinel;
    }
};

// This is new:
template <class DiffT>
struct sentinel_compare_at_t : unary_protocol {
    template <not_adaptor T>
    static bool fn(const T& self, DiffT n);

    template <adaptor IteratorAdaptorT>
    [[nodiscard]] static constexpr bool fn(const IteratorAdaptorT& adaptor, DiffT n) {
        return adaptor.iterator + n == adaptor.sentinel;
    }
};

I tried this to hack around my crash, and it seems to work.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions