c++libstdc++comparison-operatorserase-remove-idiomqcc

Different result from std::vector erase when comparing in if than comparing to stored value


For some reason, I get different result when comparing the return value from vector::erase inside a if statement or if I store the value first and then compare. But it seems to only happen with g++.

This is built on Ubuntu 20.04 AMD64 using g++, libstdc++ and with -std=c++14 flag. g++ -std=c++14 foo.cpp && ./a.out

This will return false

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

int main()
{
    std::vector<int> v{0, 1, 8, 3, 8, 5, 8, 7,8, 9};
    int thing_id{9};

    std::vector<int>::iterator const cit{
        std::remove_if(v.begin(),
                        v.end(),
                        [thing_id](int const& thing) -> bool {
                            return thing == thing_id;
                        })};

    if (v.erase(cit, v.cend()) == v.cend()) {
        std::cout << "true\n";
        return true;
    }
    else {
        std::cout << "false\n";
        return false;
    }
}

This will return true

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

int main()
{
    std::vector<int> v{0, 1, 8, 3, 8, 5, 8, 7,8, 9};
    int thing_id{9};

    std::vector<int>::iterator const cit{
        std::remove_if(v.begin(),
                        v.end(),
                        [thing_id](int const& thing) -> bool {
                            return thing == thing_id;
                        })};

    auto const prev_end = v.erase(cit, v.cend());
    if (prev_end == v.cend()) {
        std::cout << "true\n";
        return true;
    }
    else {
        std::cout << "false\n";
        return false;
    }
}

When building for QNX ARM64 with QCC and libc++, both snippets will return true.

Why is this? Should not the comparisons be deterministic either or? Can someone please explain to me what is going on here?


Solution

  • When std::vector::erase succeeds, it invalidates iterators (and references) to the elements at or after the point of the erasure. Thus the end() iterator gets invalidated as well. In other words, after the erasure will succeed, the vector will have the new cend().

    This is important because the order of evaluation of the comparison operator is unspecified, see this answer. In other words, given the expression v.erase(cit, v.cend()) == v.cend(), the compiler is free to decide to evaluate the rightmost v.cend() first, remember it, and only then evaluate the return value of v.erase(cit, v.cend()). In such a case, it would be comparing the newly returned past-the-end iterator value to the old v.cend() value (i.e., before it got invalidated by the erasure).