c++c++20std-ranges

Can I compose `take_while` and `filter` and mutate underlying sequence?


This is a follow-up to Debug assertion while iterating over composed views.

I replaced problematic transform_view with a loop and the debug assertion was gone. Then I added a filter and the problem is back:

#include <iostream>
#include <ranges>
#include <string>
#include <vector>


struct Token
{
    std::string value;
    bool used = false;
};

int main()
{
    auto tokens = std::vector{Token{"p1"}, Token{"p2"}, Token{"++"}, Token{"p3"}};

    auto view = tokens
        | std::views::drop_while([](auto const & token) { return token.used; })
        | std::views::take_while([](auto const & token) { return !token.used; })
        | std::views::filter([](auto const &) { return true; }); // without this filter it works fine

    auto strs = std::vector<std::string>();
    for (auto& elem : view)
    {
        elem.used = true;
        strs.push_back(elem.value);
    }

    for (auto const & str : strs)
    {
        std::cout << str << ", ";
    }

    return 0;
}

This time the error is (Visual Studio 2022, version 17.13.2, debug mode, /std:c++20 /MDd options): "Expression: Cannot increment filter_view iterator past end".

The filter's predicate does not depend on the pointed value, so I suppose the problem stems from its interaction with take_while. Is there a way to stay away from such pitfalls, as once again gcc and clang seem to compile and run the code just fine.

The usual goto, cppreference.com, does not provide any clue on what constrain, if any, my code violates.


Solution

  • It's the same error as before. You are changing what elements satisfy the take_while's predicate while iterating.

    The constraint you are breaking is on InputIterator. ++r has a precondition that *r be dereferenceable. A past-the-end iterator is not dereferenceable. The modification in the body of the loop makes the current iterator (unnamed because it is a range-for loop) a past-the-end iterator.

    Is there a way to stay away from such pitfalls?

    Don't do modifications that will change how your views filter the range they are operating on, whilst iterating the resulting range.

    gcc and clang seem to compile and run the code just fine

    Calling a function in std while not meeting it's precondition is undefined behaviour. Doing what you expect is a valid symptom of undefined behaviour