Заметка о map::find
Рассмотрим следующий код (C++17):
#include <map> #include <string> #include <string_view> int main() { using namespace std; map<string, int> m; string_view key = "none"; return m.find(key) != m.end(); }
Этот код не компилируется. Первая реакция: как же так? Ведь в C++14 добавили версию find, принимающую ключ произвольного типа, лишь бы значение этого типа можно было сравнивать на меньше с key_type нашего map?
Ключевое слово здесь «сравнивать». Контейнер map делает это с помощью объекта типа key_compare, который передаётся третьим (необязательным) параметром шаблона. Этот тип по умолчанию есть less<key_type> (в примере — less<string>). Так как его в примере не видно, трудно сразу догадаться о причине данной проблемы. Как нетрудно видеть, эта версия принимает только key_type, а в нашем случае string_view не приводится неявно к string. (Кстати! если вы обращаетесь к такому map, передавая в качестве ключа си-строку, например, константу "…", то она будет неявно сконвертирована в string, причём, возможно неоднократно, что будет влечь ненужный и скрытый расход вычислительных ресурсов.)
Чтобы это исправить, рекомендуется заменить версию less по умолчанию, на универсальную версию less (доступную также начиная с C++14). Следующий код компилируется и работает так, как предполагалось (кроме того, он не влечёт того скрытого расхода вычислительных ресурсов, поскольку теперь ключ не конвертируется в значение key_type):
#include <functional> #include <map> #include <string> #include <string_view> int main() { using namespace std; map<string, int, less<>> m; string_view key = "none"; return m.find(key) != m.end(); }
Оператор [] для multimap
Наверное, не только у меня бывали моменты, когда отсутствие в std::multimap возможности оперировать блоками значений, отвечающих одному ключу, хотя бы в духе совместимости с std::map раздражало и казалось странным. В C++11 добавили хэш-таблицы, включая std::unordered_multimap — аналог std::multimap (сбалансированного дерева двоичного поиска), но расширение интерфейса в сторону работы с диапазонами не последовало. К сожалению, пример boost.range и языка D (см. также ссылки здесь) пока не находят отклика в Стандартной библиотеке C++.
Несколько месяцев назад я написал расширение, добавляющее оператор [] и аналогичную функцию at() в произвольный класс, совместимый с std::multimap или std::unordered_multimap. Подмешивание функционала происходит с помощью наследования от базового контейнера, переданного параметром шаблона. Традиционно принято считать, что наследовать от стандартных контейнеров — плохо, так как они изначально для этого не предназначены (невиртуальные деструкторы). В данном случае этим стереотипом можно пренебречь, так как класс-наследник не добавляет никакого состояния к объекту базового класса и никак не влияет на его жизненный цикл. Добавляемая функциональность могла бы быть реализована внешними методами, если бы они были в C++ (есть в D).
Оператор [] возвращает прокси-объект «диапазон» MapRangeView, для которого определён ряд операций (например, присваивание, begin/end), позволяющие оперировать группой элементов multimap как контейнером с минимальным функционалом (например, обходить их с помощью for(:)). Впрочем, данный класс предоставляет также ряд высокоуровневых функций (синтаксический сахар). Но есть и свои мелкие неприятности:
map1["key1"] = map2["key2"]; // копирование прокси-объекта // содержимое map1 и map2 не поменялось
Я решил не нагружать базовый оператор присваивания подменой содержимого контейнера. Вместо этого добавлен ещё один очень простой прокси-класс BasicRange, в который оборачивается наш диапазон, если требуется скопировать или переместить его элементы (для перемещения используется std::move_iterator).
map1["key1"] = map2["key2"].copying(); // копирование элементов map1["key3"] = map2["key4"].moving(); // перемещение элементов map2["key4"].erase(); // удаление того, что осталось после перемещения
У получившегося кода есть свои недостатки, но, что более важно, я пока не пользовался им всерьёз и не могу сказать, что уверен в отсутствии опасных ошибок. Исходники: определение классов диапазонов, контейнер-обёртка multimap, простенькие тесты.