Christophe
Christophe

Reputation: 305

std::set::erase unexpectedly erases 3 elements

Before the last erase, the elements in the s1 and s2 are the same.

The last erase to the s1 erases 1 element in the set s1. It is correct.

The last erase to the s2 erases 3 elements in the set s2. It is unexpected.

#include <iostream>
#include <cstdlib>
#include <cassert>
#include <set>
#include <functional>

using namespace std;

struct Ev {
  int p = 0;
  int l = 0;
  int r = 0;
  bool open = false;

  friend ostream& operator<<(ostream& os, const Ev& ev);
};

ostream& operator<<(ostream& os, const Ev& ev) {
  os << "p = " << ev.p << ", l = " << ev.l << ", r = " << ev.r << boolalpha
     << ", open = " << ev.open;
  return os;
}

int main() {
  auto lr_cmp = [](const Ev& lhs, const Ev& rhs) {
    return lhs.r == rhs.l;
  };
  set<Ev, decltype(lr_cmp)> s1(lr_cmp);
  set<Ev, decltype(lr_cmp)> s2(lr_cmp);

  assert(s1.insert({0, 0, 1, true}).second);
  assert(s1.insert({1, 1, 3, true}).second);
  assert(s1.insert({2, 3, 4, true}).second);
  assert(s1.insert({0, 4, 5, true}).second);
  for (const auto& ev : s1) {
    cout << ev << endl;
  }
  assert(s1.erase({3, 1, 3, false}) == 1);
  
  cout << "--------------------------------" << endl;

  assert(s2.insert({1, 1, 3, true}).second);
  assert(s2.insert({1, 3, 4, true}).second);
  assert(s2.insert({0, 0, 1, true}).second);
  assert(s2.insert({0, 4, 5, true}).second);
  assert(s2.erase({2, 3, 4, false}) == 1);
  assert(s2.insert({2, 3, 4, true}).second);
  for (const auto& ev : s2) {
    cout << ev << endl;
  }
  assert(s2.erase({3, 1, 3, false}) == 1);
  return 0;
}

Here is the output.

p = 0, l = 0, r = 1, open = true
p = 1, l = 1, r = 3, open = true
p = 2, l = 3, r = 4, open = true
p = 0, l = 4, r = 5, open = true
--------------------------------
p = 0, l = 0, r = 1, open = true
p = 1, l = 1, r = 3, open = true
p = 2, l = 3, r = 4, open = true
p = 0, l = 4, r = 5, open = true
prog.exe: prog.cc:51: int main(): Assertion `s2.erase({3, 1, 3, false}) == 1' failed.
Aborted

Upvotes: 0

Views: 67

Answers (1)

Caleth
Caleth

Reputation: 62616

lr_cmp is not a strict weak order. Amongst other failings, it isn't transitive.

Requirements

if lr_cmp(a,b)==true and lr_cmp(b,c)==true then lr_cmp(a,c)==true

This is not the case, e.g.

Ev a {1, 1, 3, true};
Ev b {1, 3, 4, true};
Ev c {0, 4, 5, true};

std::cout << std::boolalpha << lr_cmp(a,b) << lr_cmp(b,c) << lr_cmp(a,c);

Thus the behaviour of your program is undefined. Presumably either it is erasing (some?) elements that are equivalent under lr_cmp, or not finding the element you want to remove.

equiv(a, b), an expression equivalent to !lr_cmp(a, b) && !lr_cmp(b, a)

{3, 1, 3, false} is equivalent to {1, 1, 3, true} and {0, 4, 5, true}.

Upvotes: 2

Related Questions