Перегрузить пользовательский компаратор в std::map

Я пытаюсь решить эту проблему. Я придумал это решение:

typedef unordered_map<string, double> stockDictType;

class StockTicker {
  class Comparator {
  public:
    inline bool operator() (const string &a, const string &b) const {
      return stocksDict.at(a) < stocksDict.at(b);
    }
  };

  stockDictType stocksDict;

  map<string, stockDictType::iterator, Comparator> stocksTicker; // this is where I need a custom comparator method

  int tickerSize;

public:
  StockTicker(int k): tickerSize(k) {}

  // some other methods
};

Как видно, это не компилируется: StockTicker::stocksDict не является статическим членом. Теперь я не могу сделать это так, потому что мне может потребоваться несколько экземпляров класса StockTicker.

std::map использует строгое определение параметра функции сравнения (std::map будет передавать только сравниваемые ключи), поэтому я не могу перегрузить его, чтобы передать ссылку на текущий экземпляр класса StockTicker (который я мог бы использовать для получения доступа на StockTicker::stocksDict через публичные геттеры)

Я черпал вдохновение из этот вопрос SO и последующий ответ для этого:

typedef unordered_map<string, double> stockDictType;

class StockTicker {
  class Comparator {
  public:
    stockDictType &_stockDictRef;

    explicit Comparator(stockDictType &stocksDict): _stockDictRef(stocksDict) {}

    inline bool operator() (const string &a, const string &b) const {
      return _stockDictRef.at(a) < _stockDictRef.at(b);
    }
  };

  stockDictType stocksDict;
  map<string, stockDictType::iterator, Comparator> stocksTicker(Comparator{stocksDict});
  int tickerSize;

public:
  StockTicker(int k): tickerSize(k) {}

  void addOrUpdate(string name, double price) {
    stocksDict[name] = price;
    stocksTicker.at(name) = stocksDict.find(name);
  }

  vector<stockDictType::iterator> top() {
    vector<stockDictType::iterator> ret(tickerSize);

    auto it = stocksTicker.begin();
    for(int i = 0; i < tickerSize; i++, it++)
      ret[i] = it->second;

    return ret;
  }
};

Это тоже не скомпилируется. Я получаю эту ошибку в методах StockTicker::addOrUpdate() и StockTicker::top(): error: '((StockTicker*)this)->StockTicker::stocksTicker' does not have class type.

Я также пробовал кучу других вещей (например, объявление общедоступного метода компаратора в самом классе StockTicker и попытка передать указатель на его функцию в std::map. Это также не удалось; StockTicker::stocksTicker объявляется до того, как метод компаратора делает это, и компилятор жалуется) .

Любые идеи о том, как это исправить?


person Quirk    schedule 31.03.2017    source источник


Ответы (1)


 std::map<std::string, stockDictType::iterator, Comparator> stocksTicker(Comparator(stocksDict));

это определяет функцию-член с именем stocksTicker, которая принимает аргумент stocksDict типа Comparator и возвращает std::map.

std::map<std::string, stockDictType::iterator, Comparator> stocksTicker{Comparator{stocksDict}};

Это определяет переменную-член stocksTicker, которая по умолчанию инициализируется с помощью Comparator, которая, в свою очередь, была инициализирована с помощью переменной-члена stocksDict.

Я предполагаю, что вы хотите второго.

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

Живой пример

Вы должны StockTicker(StockTicker &&)=delete и StockTicker& operator=(StockTicker &&)=delete, так как карты, содержащие ссылки на содержащий их класс, небезопасно перемещать или копировать.

Создать эффективный ход здесь сложно. Я подозреваю, что сращивание узлов C++17 может позволить это сделать. Возможно, вам придется внедрить std::shared_ptr<stocksDict*> (да, общий указатель на указатель) и использовать .key_comp для переустановки stocksDict в целевом объекте.

person Yakk - Adam Nevraumont    schedule 31.03.2017
comment
Это работает отлично. Хотя последние два абзаца не имеют для меня никакого смысла. Где-нибудь я могу найти больше объяснений по этому поводу? - person Quirk; 31.03.2017
comment
@Quirk Если у вас есть struct foo и внутри foo вы сохраняете указатель назад на структуру, операторы перемещения/копирования по умолчанию приведут к тому, что копируемое в foo будет содержать указатель назад на структуру оригинальный фу. Вот так: struct foo { foo* self; foo():self(this) {} }; foo f1; foo f2=f1; теперь f2.self == &f1; что обычно не то, что вам нужно. Ваш компаратор на карте фактически имеет указатель на объект, содержащий карту (ну, на поле-член в объекте). 2-й последний абзац говорит, что простой способ исправить это — запретить перемещение/копирование. - person Yakk - Adam Nevraumont; 31.03.2017
comment
Спасибо! Мне также было интересно, почему это не сработало: std::map<std::string, stockDictType::iterator, Comparator> stocksTicker((Comparator(stocksDict))); Согласно этому ответу, это должно было обойти проблему синтаксиса, разве не так ли? - person Quirk; 31.03.2017
comment
@quirk () не разрешены во встроенной переменной init в классе. Используйте {}с - person Yakk - Adam Nevraumont; 31.03.2017