Причина, по которой вы не можете изменить x2
, заключается в том, что она объявлена const
, как было указано @dasblinkenlight. Комментарий @ songyuanyao является правильным для доступа к объекту, на который ссылается итератор, но не вполне отвечает на вопрос, потому что он не говорит , почему set итераторы разрешают только const
доступ.
Причина этого в том, что, как вы знаете, std::set
- это упорядоченный контейнер, структура которого определяется путем сравнения записей друг с другом с использованием (по умолчанию) operator <
. Это означает, что существует контейнер , инвариант , такой, что если элемент a
предшествует другому элементу b
в std::set
, то следует !(b < a)
. Я сказал так, потому что это также верно для std::multiset
. Поскольку в наборе дубликаты недопустимы, из этого следует, что на самом деле, если a
предшествует b
, то a < b
. Если этот инвариант был нарушен, то любая операция набора, которая требует сортировки набора, например find
или insert
, будет иметь неожиданное (точнее, неопределенное) поведение.
Следовательно, std::set
не позволит вам изменить элемент, используя iterator
, потому что вы можете невольно изменить элементы элемента таким образом, чтобы это влияло на его правильное место в наборе, нарушая инвариант и, таким образом, вызывая неопределенное поведение.
К сожалению, компилятор недостаточно умен, чтобы понимать, что ваша функция сравнения оценивает только определенные элементы, в данном случае только id
. Если бы компилятор и язык были способны анализировать и выражать это, они могли бы подумать, что хотя i->id
должно быть ссылкой на const
, i->m
для любого другого члена m
может безопасно быть ссылкой на не const
.
Существует как минимум четыре возможных решения вашей проблемы:
- Самое простое, но самое уродливое решение - пометить участников, которых нужно изменить, как
mutable
. Обратите внимание, что вы сами должны убедиться, что их изменение не влияет на порядок сортировки.
- Другим довольно уродливым решением является преднамеренное использование
const_cast
, как в const_cast<mystruct &>(*i)
, где i
- итератор. Опять же, никогда не меняйте элемент, который влияет на порядок сортировки таким образом.
- Более элегантное решение, которое, однако, имеет дополнительные накладные расходы времени выполнения, заключается в добавлении уровня косвенности указателя (например, с использованием
std::unique_ptr
) к свойствам, которые вы хотите изменить. Учтите, однако, что если бы вы использовали pointee в своей функции сравнения, вы все равно рискуете нарушить установленный инвариант, только теперь компилятор и библиотека больше не будут препятствовать вам в этом!
- Единственный способ, который работает даже , если вы хотите изменить элементы, влияющие на порядок сортировки, - это сделать копию элемента, изменить копию, стереть старый элемент, а затем повторно вставить копию , Это позволяет контейнеру вставить копию в другое положение, если необходимо.
Заключительные ноты:
- Хеш-контейнер, такой как
std::unordered_set
, будет иметь точно такую же проблему, только в этом случае это не функция сравнения, а функции хеширования и равенства, которые вы должны учитывать.
- По указанным причинам
std::map
или std::unordered_map
могут лучше подходить для вашей проблемной области, поскольку в этом случае библиотека знает, что mapped_type
не используется для определения структуры контейнера. Также обратите внимание, что value_type
для std::map
или std::unordered_map
равно std::pair<const key_type, mapped_type>
вместо std::pair<key_type, mapped_type>
точно по той же причине, по которой изменение ключа может нарушить инварианты контейнера.