Способ замены постоянного элемента в карте с помощью итератора - PullRequest
0 голосов
/ 14 января 2020

У меня есть такой код:

std::map<int, const Data> all_data;
// ...
bool OldDataIsBetter(Data const& oldData, Data const& newData) {...}
// ...
void AddData(int key, Data&& newData)
{
    auto [it, ok] = all_data.try_emplace(key, std::move(newData));
    if (!ok)
    {
        if (OldDataIsBetter(*it, newData)) return;
        it->second = std::move(newData);
    }
}

Это не компилируется, потому что it->second относится к const Data и поэтому его оператор присваивания не может быть вызван. Это прекрасно работает, если const удалено.

Цель вышеизложенного - insert_or_assign, за исключением того, что если элемент уже присутствует, мне нужно сравнить старый и новый элементы, чтобы увидеть, какой из них «лучше».

Цель объявления типа элемента карты с помощью const состоит в том, что данные должны быть неизменными один раз на карте - элемент в целом может быть заменен, но не может быть изменен по частям.

Я могу «исправить» вышеприведенное , переназначив контейнер вместо:

all_data[key] = std::move(newData);

(Фактически получается, что та же проблема с const.)

Или стирая и повторяя emplace:

all_data.erase(it);
all_data.emplace(key, std::move(newData)); // should never fail

Но ни один из них не выглядит элегантным, так как у меня уже есть итератор, указывающий на элемент, который должен быть заменен, и оба из вышеперечисленных забудьте об этом и go выполните поиск снова.

Есть ли лучший способ выполнить sh эту замену?


TLDR из цепочки чата поднял связанные вопросы:

  • Если extract, можно Извлеките узел из контейнера, позвольте вам изменить его иначе-const ключ и заново вставить его - все без каких-либо перераспределений - почему это невозможно для const mapped_value?
  • Const объекты могут быть уничтожены. Это также должно применяться к const-объектам внутри контейнеров - и это действительно то, что может делать вариант erase / emplace, если случается повторное использование одного и того же хранилища для узла (либо по совпадению, либо через пользовательский распределитель). Почему же тогда нет способа replace const mapped_value с другим const mapped_value без перераспределения узла карты, в котором он находится?

Ответы [ 3 ]

2 голосов
/ 14 января 2020

const означает неизменный, не частично изменяемый. Если вы используете const в объявлении объекта (это то, что вы делаете, когда вставляете const в этот параметр шаблона), C ​​++ считает, что вы подразумеваете это . И это вас удержит.

Так что const Data - это не стартер.

Исходя из вашего вопроса, я предполагаю, что Data имеет некоторые функции, которые устанавливают некоторые части его состояния. и вы не хотите, чтобы пользователи вызывали указанные функции. Но вы хотите, чтобы пользователь мог перезаписывать значение.

Способ сделать это - предоставить тип объекта, который оборачивает экземпляр Data, позволяя присваивать объект Data. Эта оболочка будет предоставлять версии const аксессоров, которые предоставляет Data. Но тип иначе не предоставляет не-1018 * модификаторы Data.

1 голос
/ 14 января 2020

В вашем случае, что вы действительно хотите сделать, это условно перезаписать элемент данных.

Это строго аргументирует для изменяемого элемента данных.

В общем случае, Контейнер должен иметь неизменяемые элементы, поэтому вы можете предоставить оболочку, которая обеспечивает постоянный доступ, если только не требуется изменяемый доступ:

#include <map>
#include <functional>

struct Data {};

struct MyDataMap
{
    using store_type = std::map<int, Data>;
    using iterator = store_type::const_iterator;

    template<class Condition>
    std::pair<iterator, bool> 
    replace_if(int key, Data&& value, Condition cond)
    {
        auto [it, inserted_or_replaced] = store_.try_emplace(key, std::move(value));
        if (!inserted_or_replaced && cond(it->second, value))
        {
            it->second = std::move(value);
            inserted_or_replaced = true;
        }
        return std::make_pair(it, inserted_or_replaced);
    }

    // other accessors as necessary

    iterator begin() const { return store_.cbegin(); }
    iterator end() const { return store_.cend(); }

private:

    store_type store_;
};

// ...
bool OldDataIsBetter(Data const& oldData, Data const& newData);
// ...

void test(MyDataMap& m, int k, Data newd)
{
    auto oldIsWorse = std::not_fn(OldDataIsBetter);
    auto [it, replaced] = m.replace_if(k, std::move(newd), oldIsWorse);    
}
0 голосов
/ 14 января 2020

После некоторых дальнейших экспериментов я думаю, что нижеприведенный метод является лучшим из доступных на данный момент для этого сценария:

it = all_data.erase(it);
all_data.emplace_hint(it, key, std::move(newData)); // should never fail

Это все еще не кажется мне идеальным, поскольку все равно перераспределяет узел карты. Это не конец света, но я хотел бы найти способ избежать этого, если это возможно.

...