Boost.Container flat_map и std::string_view

Некоторое время я использовал Boost flat_map в качестве моей ассоциативной коллекции по причинам, объясненным в их вступлении к документации, и (первоначально) тому факту, что он предоставлял новые функции до реализации стандартного компилятора, и это было то же самое во всех платформы.

Теперь я хотел начать использовать string_view для предотвращения копирования строк, когда они берутся из подстрок большего ввода. string_view указывает на диапазон символов в большей строке без необходимости копировать их в новый экземпляр std::string.

В поисках карты для использования я вспомнил, что еще одна прогрессивная функция Boost.Container, которая мне нравилась в прошлом, — это конформные ключи, где вы можете использовать все, что правильно сравнивается с сохраненным ключом, вместо преобразования в фактический тип ключа.

Но сейчас я не могу найти упоминания об этом в документации. Я знаю, что std::map может сделать это сейчас (начиная с C++14), но я бы предпочел использовать flat_map для крошечных коллекций.

Что я мог увидеть, что позволяло такую ​​гибкость много лет назад, если это не очевидно в boost::flat_map::insert и т. д.? Какие плоские коллекции лучше использовать сейчас с современными компиляторами?


person JDługosz    schedule 10.05.2018    source источник
comment
Гетерогенный поиск и совместимый ключ действительно являются функциями, которые существуют в частях boost (например, Boost multi index). Возможно, эти ключевые слова помогут вам найти то, что вы, кажется, помните. В остальном ответ Андрея отличный   -  person sehe    schedule 10.05.2018
comment
Мультииндекс: может быть, это то, где я это видел, так как это то, что я также использовал. Спасибо.   -  person JDługosz    schedule 11.05.2018


Ответы (2)


Поддержка функций полиморфного поиска была добавлена ​​в Boost.Container недавно. Если все в порядке, следует выпустить с Boost 1.68.

Тем временем вы можете эмулировать плоские ассоциативные контейнеры с упорядоченными std::vector и std::lower_bound.

typedef std::pair< std::string, int > element_type;
std::vector< element_type > map;

struct element_order
{
    bool operator()(element_type const& left, element_type const& right) const
    {
        return left.first < right.first;
    }

    bool operator()(std::string_view const& left, element_type const& right) const
    {
        return left < right.first;
    }

    bool operator()(element_type const& left, std::string_view const& right) const
    {
        return left.first < right;
    }
};

auto find_element(std::string_view const& key)
{
    auto it = std::lower_bound(map.begin(), map.end(), key, element_order());
    if (it != map.end() && it->first == key)
        return it;
    return map.end();
}
person Andrey Semashev    schedule 10.05.2018
comment
Зачем вам нужно element_order вместо того, чтобы просто использовать less<>? - person JDługosz; 11.05.2018
comment
Потому что std::less не является полиморфным функциональным объектом. Он требует, чтобы его аргументы были такими же, как аргумент шаблона. - person Andrey Semashev; 11.05.2018
comment
На самом деле, похоже, что поскольку C++14 std::less<void> (и, что то же самое, std::less<>) является полиморфным. Но вы по-прежнему не можете его использовать, потому что вам нужно получить доступ к элементу element_type, а std::less попытается сравнить весь element_type с ключом. - person Andrey Semashev; 11.05.2018

Возможно, это не то, о чем вы говорите, но если вы используете std::string_view в качестве типа ключа, все операции уже работают через неявное преобразование в std::string_view:

Жить на Coliru

#include <boost/container/flat_map.hpp>
#include <string_view>

int main() {
    boost::container::flat_map<std::string_view, int> m {
        { "one", 1 },
        { "two", 2 },
        { "three", 3 },
        { "four", 4 },
    };

    std::string key = "one";
    auto one = m.at(key);
    auto range = m.equal_range(key);
    auto it = m.find(key);

    m[key] = 1;
}

Обратное

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

Вот один:

Прямой эфир на Coliru

#include <initializer_list>
#include <algorithm>
#include <utility>
#include <stdexcept>

#include <boost/container/small_vector.hpp>

template <typename K, typename V, typename Cmp = std::less<K>, typename Storage = boost::container::small_vector<std::pair<K, V>, 10> >
struct flat_map {
    using key_type       = K;
    using mapped_type    = V;
    using key_compare    = Cmp;

    using storage        = Storage;
    using value_type     = typename storage::value_type;
    using iterator       = typename Storage::iterator;
    using const_iterator = typename Storage::const_iterator;

    struct value_compare {
        key_compare _cmp;
        template <typename A, typename B>
        bool operator()(A const& a, B const& b) const { return _cmp(access(a), access(b)); }

      private:
        static auto& access(value_type const& v) { return v.first; }
        template <typename Other>
        static auto& access(Other const& v)      { return v; }
    } _cmp;

    storage _data;

    flat_map(std::initializer_list<value_type> i) : _data(i) {}

    iterator begin()             { return _data.begin(); }
    iterator end()               { return _data.end();   }
    const_iterator begin() const { return _data.begin(); }
    const_iterator end()   const { return _data.end();   }

    template <typename Key>
    mapped_type& operator[](Key&& key) { return find(std::forward<Key>(key))->second; }
    template <typename Key>
    mapped_type const& operator[](Key&& key) const { return find(std::forward<Key>(key))->second; }

    template <typename Key>
    iterator find(Key&& key) {
        auto r = equal_range(std::forward<Key>(key));
        return (r.first == r.second)? end() : r.first;
    }
    template <typename Key>
    const_iterator find(Key&& key) const {
        auto r = equal_range(std::forward<Key>(key));
        return (r.first == r.second)? end() : r.first;
    }

    template <typename Key>
    mapped_type& at(Key&& key) {
        auto r = equal_range(std::forward<Key>(key));
        if (r.first == r.second) throw std::out_of_range("key");
        return r.first->second;
    }
    template <typename Key>
    mapped_type const& at(Key&& key) const {
        auto r = equal_range(std::forward<Key>(key));
        if (r.first == r.second) throw std::out_of_range("key");
        return r.first->second;
    }

    template <typename Key>
    auto equal_range(Key&& key) { return std::equal_range(begin(), end(), std::forward<Key>(key), _cmp); }
    template <typename Key>
    auto equal_range(Key&& key) const { return std::equal_range(begin(), end(), std::forward<Key>(key), _cmp); }
};

Он поддерживает точно обратный первому сценарию (с учетом компаратора std::less<>):

#include <string_view>
#include <string>

int main() {
    flat_map<std::string, int, std::less<> > m {
        { "one", 1 },
        { "two", 2 },
        { "three", 3 },
        { "four", 4 },
    };

    std::string_view key = "one";
    auto one = m.at(key);
    auto range = m.equal_range(key);
    auto it = m.find(key);

    m[key] = 1;
}
person sehe    schedule 10.05.2018
comment
Я думаю, что использование string_view в качестве типа ключа — очень плохая идея, поскольку он не владеет хранилищем. (Однако подходит для ограниченного показа, если вы контролируете, как добавляются вещи; может быть, перекрестный указатель для другой коллекции) - person JDługosz; 11.05.2018
comment
Что-то не является плохой идеей только потому, что есть ситуации, в которых это неприменимо. Это просто не применимо ко всем ситуациям. (Кстати, я только что исправил ошибку, которую понял после того, как лёг спать. Ура :)) - person sehe; 11.05.2018
comment
Я не понимаю, как может помочь передача другой коллекции в качестве аргумента шаблона в flat_map. - person JDługosz; 11.05.2018
comment
Никто этого не сказал. Я думаю, что вы, возможно, еще не прочитали ответ полностью, поэтому я призываю вас сделать это, и я увижу ваши комментарии утром. Спокойной ночи! - person sehe; 11.05.2018
comment
Вы сказали: Аргумент контейнера хранилища для flat_map — лучшая идея, чем написание словаря на основе отсортированного вектора, который имеет шаблонный член find. Что я не до конца прочитал? - person JDługosz; 11.05.2018
comment
О, просто замешательство. Вы ответили на две отдельные части моего ответа (stringview-as-key и сноску) - не упомянув большую часть (The Inverse, у которой есть вторая живая демонстрация). Неважно, кажется, что мое использование сноски сбивало с толку (это связано только с моим выбором типа storage, на котором сноска закреплена¹). Я изменил ответ, чтобы удалить это (и вместо этого показать, что я имел в виду в сноске). - person sehe; 11.05.2018