Почему я не могу удалить строку из std::set с помощью std::remove if? [дубликат]

Возможные Дубликаты:
remove_if эквивалент для std:: map

у меня есть набор строк:

set <wstring> strings;
// ...

Я хочу удалить строки в соответствии с предикатом, например:

std::remove_if ( strings.begin(), strings.end(), []( const wstring &s ) -> bool { return s == L"matching"; });

когда я пытаюсь это сделать, я получаю следующую ошибку компилятора:

c:Program Files (x86)Microsoft Visual Studio 10.0VCincludealgorithm(1840): error C2678: binary '=' : no operator found which takes a left-hand operand of type 'const std::basic_string<_Elem,_Traits,_Ax>' 

ошибки, по-видимому, предполагает, что std::string не имеет конструктора копирования по значению (что было бы незаконным). Это как-то плохо использовать std::remove_if С std::set ? Должен ли я делать что-то другое, например, несколько итераций set::find() следовал по set::erase() ?

2 ответов


std::remove_if (или std::erase) работает путем переназначения значений членов диапазона. Он не понимает, как std::set организует данные или как удалить узел из его внутренней структуры данных дерева. Действительно, это невозможно сделать, используя только ссылки на узлы, не имея set сам объект.

стандартные алгоритмы разработаны, чтобы иметь прозрачные (или, по крайней мере, последовательно легко запоминающиеся) вычислительные сложности. Функция выборочного удаления элементы set будет O (N log N), из-за необходимости перебалансировать дерево, что не лучше, чем вызов цикла my_set.remove() . Таким образом, стандарт не предоставляет его, и это то, что вам нужно написать.

С другой стороны, наивно вручную петля для удаления элементов vector один за другим будет O (N^2), тогда как std::remove_if является O (N). Таким образом, библиотека дает ощутимую выгоду в этом случае.

типичный цикл (C++03 style):

for ( set_t::iterator i = my_set.begin(); i != my_set.end(); ) {
    if ( condition ) {
        my_set.erase( i ++ ); // strict C++03
        // i = my_set.erase( i ); // more modern, typically accepted as C++03
    } else {
        ++ i; // do not include ++ i inside for ( )
    }
}

Edit (4 года спустя!): i ++ выглядит подозрительно. Что, если ... --10--> недействительным i прежде чем оператор пост-инкремента сможет его обновить? Это хорошо, хотя, потому что это перегруженный operator++ а не встроенный оператор. Функция безопасно обновляет i и затем возвращает копию исходного значения.


сообщение об ошибке говорит

не найден оператор, который принимает левый операнд типа' const std:: basic_string<_elem>'

обратите внимание на const. Компилятор прав, что std::wstring нет operator= который можно вызвать для объекта const.

почему строка const? Ответ заключается в том, что значения std::set неизменяемы, потому что значения в наборе упорядочены, и изменение значения может измените его порядок в наборе, сделав набор недействительным.

почему компилятор пытается скопировать значение из набора?

std::remove_ifstd::remove) на самом деле ничего не стирают (и не могут, потому что у них нет контейнера, только итераторы). Что они делают, так это копировать все значения в диапазоне, который не сопоставьте критерий с началом диапазона и верните итератор следующему элементу после соответствующих элементов. Тогда вы должны вручную стереть из возвращаемого итератора до конца диапазона. Поскольку набор сохраняет свои элементы в порядке, было бы неправильно перемещать какие-либо элементы, поэтому remove_if не может использоваться в наборе (или любом другом ассоциативном контейнере).

короче говоря, вам нужно использовать цикл std::find_if и set::erase, например:

template<class V, class P>
void erase_if(std::set<V>& s, P p)
{
  std::set<V>::iterator e = s.begin();
  for (;;)
  {
    e = std::find_if(e, s.end(), p);
    if (e == s.end())
      break;
    e = s.erase(e);
  }
}