vectorscan/src/util/flat_containers.h
Justin Viiret 9cf66b6ac9 util: switch from Boost to std::unordered set/map
This commit replaces the ue2::unordered_{set,map} types with their STL
versions, with some new hashing utilities in util/hash.h. The new types
ue2_unordered_set<T> and ue2_unordered_map<Key, T> default to using the
ue2_hasher.

The header util/ue2_containers.h has been removed, and the flat_set/map
containers moved to util/flat_containers.h.
2017-08-21 11:14:55 +10:00

665 lines
21 KiB
C++

/*
* Copyright (c) 2015-2017, Intel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Intel Corporation nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef UTIL_FLAT_CONTAINERS_H
#define UTIL_FLAT_CONTAINERS_H
#include "ue2common.h"
#include "util/hash.h"
#include "util/operators.h"
#include "util/small_vector.h"
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <utility>
#include <boost/iterator/iterator_facade.hpp>
namespace ue2 {
namespace flat_detail {
// Iterator facade that wraps an underlying iterator, so that we get our
// own iterator types.
template <class WrappedIter, class Value>
class iter_wrapper
: public boost::iterator_facade<iter_wrapper<WrappedIter, Value>, Value,
boost::random_access_traversal_tag> {
public:
iter_wrapper() = default;
explicit iter_wrapper(WrappedIter it_in) : it(std::move(it_in)) {}
// Templated copy-constructor to allow for interoperable iterator and
// const_iterator.
private:
template <class, class> friend class iter_wrapper;
public:
template <class OtherIter, class OtherValue>
iter_wrapper(iter_wrapper<OtherIter, OtherValue> other,
typename std::enable_if<std::is_convertible<
OtherIter, WrappedIter>::value>::type * = nullptr)
: it(std::move(other.it)) {}
WrappedIter get() const { return it; }
private:
friend class boost::iterator_core_access;
WrappedIter it;
void increment() { ++it; }
void decrement() { --it; }
void advance(size_t n) { it += n; }
typename std::iterator_traits<WrappedIter>::difference_type
distance_to(const iter_wrapper &other) const {
return other.it - it;
}
bool equal(const iter_wrapper &other) const { return it == other.it; }
Value &dereference() const { return *it; }
};
template <class T, class Compare, class Allocator>
class flat_base {
protected:
// Underlying storage is a small vector with local space for one element.
using storage_type = small_vector<T, 1, Allocator>;
using storage_alloc_type = typename storage_type::allocator_type;
// Putting our storage and comparator in a tuple allows us to make use of
// the empty base class optimization (if this STL implements it for
// std::tuple).
std::tuple<storage_type, Compare> storage;
flat_base(const Compare &compare, const Allocator &alloc)
: storage(storage_type(storage_alloc_type(alloc)), compare) {}
storage_type &data() { return std::get<0>(this->storage); }
const storage_type &data() const { return std::get<0>(this->storage); }
Compare &comp() { return std::get<1>(this->storage); }
const Compare &comp() const { return std::get<1>(this->storage); }
public:
// Common member types.
using key_compare = Compare;
Allocator get_allocator() const {
return data().get_allocator();
}
key_compare key_comp() const {
return comp();
}
// Capacity.
bool empty() const { return data().empty(); }
size_t size() const { return data().size(); }
size_t max_size() const { return data().max_size(); }
// Modifiers.
void clear() {
data().clear();
}
void swap(flat_base &a) {
using std::swap;
swap(comp(), a.comp());
swap(data(), a.data());
}
};
} // namespace flat_detail
/**
* \brief Set container implemented internally as a sorted vector. Use this
* rather than std::set for small sets as it's faster, uses less memory and
* incurs less malloc time.
*
* Note: we used to use boost::flat_set, but have run into problems with all
* the extra machinery it instantiates.
*/
template <class T, class Compare = std::less<T>,
class Allocator = std::allocator<T>>
class flat_set
: public flat_detail::flat_base<T, Compare, Allocator>,
public totally_ordered<flat_set<T, Compare, Allocator>> {
using base_type = flat_detail::flat_base<T, Compare, Allocator>;
using storage_type = typename base_type::storage_type;
using storage_iterator = typename storage_type::iterator;
using storage_const_iterator = typename storage_type::const_iterator;
using base_type::data;
using base_type::comp;
#if defined(SMALL_VECTOR_IS_STL_VECTOR)
// Construct a non-const iterator from a const iterator. Used in flat_map
// and flat_set erase() calls to work around g++-4.8 compatibility issues.
storage_iterator mutable_iterator(storage_const_iterator it) {
return data().begin() + std::distance(data().cbegin(), it);
}
#endif
public:
// Member types.
using key_type = T;
using value_type = T;
using size_type = typename storage_type::size_type;
using difference_type = typename storage_type::difference_type;
using key_compare = typename base_type::key_compare;
using value_compare = Compare;
using allocator_type = Allocator;
using reference = value_type &;
using const_reference = const value_type &;
using allocator_traits_type = typename std::allocator_traits<Allocator>;
using pointer = typename allocator_traits_type::pointer;
using const_pointer = typename allocator_traits_type::const_pointer;
// Iterator types.
using iterator = flat_detail::iter_wrapper<typename storage_type::iterator,
const value_type>;
using const_iterator =
flat_detail::iter_wrapper<typename storage_type::const_iterator,
const value_type>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// Constructors.
flat_set(const Compare &compare = Compare(),
const Allocator &alloc = Allocator())
: base_type(compare, alloc) {}
template <class InputIt>
flat_set(InputIt first, InputIt last, const Compare &compare = Compare(),
const Allocator &alloc = Allocator())
: flat_set(compare, alloc) {
insert(first, last);
}
flat_set(std::initializer_list<value_type> init,
const Compare &compare = Compare(),
const Allocator &alloc = Allocator())
: flat_set(compare, alloc) {
insert(init.begin(), init.end());
}
flat_set(const flat_set &) = default;
flat_set(flat_set &&) = default;
flat_set &operator=(const flat_set &) = default;
flat_set &operator=(flat_set &&) = default;
// Iterators.
iterator begin() { return iterator(data().begin()); }
const_iterator cbegin() const { return const_iterator(data().cbegin()); }
const_iterator begin() const { return cbegin(); }
iterator end() { return iterator(data().end()); }
const_iterator cend() const { return const_iterator(data().cend()); }
const_iterator end() const { return cend(); }
reverse_iterator rbegin() { return reverse_iterator(end()); }
const_reverse_iterator crbegin() const {
return const_reverse_iterator(cend());
}
const_reverse_iterator rbegin() const { return crbegin(); }
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator crend() const {
return const_reverse_iterator(cbegin());
}
const_reverse_iterator rend() const { return crend(); }
// Modifiers.
std::pair<iterator, bool> insert(const value_type &value) {
auto it = std::lower_bound(data().begin(), data().end(), value, comp());
if (it == data().end() || comp()(value, *it)) {
return std::make_pair(iterator(data().insert(it, value)), true);
}
return std::make_pair(iterator(it), false);
}
iterator insert(UNUSED const_iterator hint, const value_type &value) {
return insert(value).first;
}
std::pair<iterator, bool> insert(value_type &&value) {
auto it = std::lower_bound(data().begin(), data().end(), value, comp());
if (it == data().end() || comp()(value, *it)) {
return std::make_pair(iterator(data().insert(it, std::move(value))),
true);
}
return std::make_pair(iterator(it), false);
}
iterator insert(UNUSED const_iterator hint, value_type &&value) {
return insert(value).first;
}
template <class InputIt>
void insert(InputIt first, InputIt second) {
for (; first != second; ++first) {
insert(*first);
}
}
void insert(std::initializer_list<value_type> ilist) {
insert(ilist.begin(), ilist.end());
}
template<class...Args>
std::pair<iterator, bool> emplace(Args&&... args) {
return insert(value_type(std::forward<Args>(args)...));
}
void erase(const_iterator pos) {
#if defined(SMALL_VECTOR_IS_STL_VECTOR)
// Cope with libstdc++ 4.8's incomplete STL (it's missing C++11
// vector::erase(const_iterator)) by explicitly using a non-const
// iterator.
auto pos_it = mutable_iterator(pos.get());
#else
auto pos_it = pos.get();
#endif
data().erase(pos_it);
}
void erase(const_iterator first, const_iterator last) {
#if defined(SMALL_VECTOR_IS_STL_VECTOR)
// As above, work around libstdc++ 4.8's incomplete C++11 support.
auto first_it = mutable_iterator(first.get());
auto last_it = mutable_iterator(last.get());
#else
auto first_it = first.get();
auto last_it = last.get();
#endif
data().erase(first_it, last_it);
}
void erase(const key_type &key) {
auto it = find(key);
if (it != end()) {
erase(it);
}
}
// Lookup.
size_type count(const value_type &value) const {
return find(value) != end() ? 1 : 0;
}
iterator find(const value_type &value) {
auto it = std::lower_bound(data().begin(), data().end(), value, comp());
if (it != data().end() && comp()(value, *it)) {
it = data().end();
}
return iterator(it);
}
const_iterator find(const value_type &value) const {
auto it = std::lower_bound(data().begin(), data().end(), value, comp());
if (it != data().end() && comp()(value, *it)) {
it = data().end();
}
return const_iterator(it);
}
// Observers.
value_compare value_comp() const {
return comp();
}
// Operators. All others provided by ue2::totally_ordered.
bool operator==(const flat_set &a) const {
return data() == a.data();
}
bool operator<(const flat_set &a) const {
return data() < a.data();
}
// Free swap function for ADL.
friend void swap(flat_set &a, flat_set &b) {
a.swap(b);
}
};
/**
* \brief Map container implemented internally as a sorted vector. Use this
* rather than std::map for small maps as it's faster, uses less memory and
* incurs less malloc time.
*
* Note: we used to use boost::flat_map, but have run into problems with all
* the extra machinery it instantiates.
*
* Note: ue2::flat_map does NOT provide mutable iterators, as (given the way
* the data is stored) it is difficult to provide a real mutable iterator that
* wraps std::pair<const Key, T>. Instead, all iterators are const, and you
* should use flat_map::at() or flat_map::operator[] to mutate the contents of
* the container.
*/
template <class Key, class T, class Compare = std::less<Key>,
class Allocator = std::allocator<std::pair<Key, T>>>
class flat_map
: public flat_detail::flat_base<std::pair<Key, T>, Compare, Allocator>,
public totally_ordered<flat_map<Key, T, Compare, Allocator>> {
public:
// Member types.
using key_type = Key;
using mapped_type = T;
using value_type = std::pair<const Key, T>;
private:
using base_type =
flat_detail::flat_base<std::pair<Key, T>, Compare, Allocator>;
using keyval_storage_type = std::pair<key_type, mapped_type>;
using storage_type = typename base_type::storage_type;
using storage_iterator = typename storage_type::iterator;
using storage_const_iterator = typename storage_type::const_iterator;
using base_type::data;
using base_type::comp;
#if defined(SMALL_VECTOR_IS_STL_VECTOR)
// Construct a non-const iterator from a const iterator. Used in flat_map
// and flat_set erase() calls to work around g++-4.8 compatibility issues.
storage_iterator mutable_iterator(storage_const_iterator it) {
return data().begin() + std::distance(data().cbegin(), it);
}
#endif
public:
// More Member types.
using size_type = typename storage_type::size_type;
using difference_type = typename storage_type::difference_type;
using key_compare = typename base_type::key_compare;
using allocator_type = Allocator;
using reference = value_type &;
using const_reference = const value_type &;
using allocator_traits_type = typename std::allocator_traits<Allocator>;
using pointer = typename allocator_traits_type::pointer;
using const_pointer = typename allocator_traits_type::const_pointer;
public:
using const_iterator =
flat_detail::iter_wrapper<typename storage_type::const_iterator,
const keyval_storage_type>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// All iterators are const for flat_map.
using iterator = const_iterator;
using reverse_iterator = const_reverse_iterator;
// Constructors.
flat_map(const Compare &compare = Compare(),
const Allocator &alloc = Allocator())
: base_type(compare, alloc) {}
template <class InputIt>
flat_map(InputIt first, InputIt last, const Compare &compare = Compare(),
const Allocator &alloc = Allocator())
: flat_map(compare, alloc) {
insert(first, last);
}
flat_map(std::initializer_list<value_type> init,
const Compare &compare = Compare(),
const Allocator &alloc = Allocator())
: flat_map(compare, alloc) {
insert(init.begin(), init.end());
}
flat_map(const flat_map &) = default;
flat_map(flat_map &&) = default;
flat_map &operator=(const flat_map &) = default;
flat_map &operator=(flat_map &&) = default;
// Iterators.
const_iterator cbegin() const { return const_iterator(data().cbegin()); }
const_iterator begin() const { return cbegin(); }
const_iterator cend() const { return const_iterator(data().cend()); }
const_iterator end() const { return cend(); }
const_reverse_iterator crbegin() const {
return const_reverse_iterator(cend());
}
const_reverse_iterator rbegin() const { return crbegin(); }
const_reverse_iterator crend() const {
return const_reverse_iterator(cbegin());
}
const_reverse_iterator rend() const { return crend(); }
private:
storage_iterator data_lower_bound(const key_type &key) {
return std::lower_bound(
data().begin(), data().end(), key,
[&](const keyval_storage_type &elem, const key_type &k) {
return comp()(elem.first, k);
});
}
storage_const_iterator
data_lower_bound(const key_type &key) const {
return std::lower_bound(
data().begin(), data().end(), key,
[&](const keyval_storage_type &elem, const key_type &k) {
return comp()(elem.first, k);
});
}
std::pair<storage_iterator, bool> data_insert(const value_type &value) {
auto it = data_lower_bound(value.first);
if (it == data().end() || comp()(value.first, it->first)) {
return std::make_pair(data().insert(it, value), true);
}
return std::make_pair(it, false);
}
std::pair<storage_iterator, bool> data_insert(value_type &&value) {
auto it = data_lower_bound(value.first);
if (it == data().end() || comp()(value.first, it->first)) {
return std::make_pair(data().insert(it, std::move(value)), true);
}
return std::make_pair(it, false);
}
storage_iterator data_find(const key_type &key) {
auto it = data_lower_bound(key);
if (it != data().end() && comp()(key, it->first)) {
it = data().end();
}
return it;
}
storage_const_iterator data_find(const key_type &key) const {
auto it = data_lower_bound(key);
if (it != data().end() && comp()(key, it->first)) {
it = data().end();
}
return it;
}
public:
// Modifiers.
std::pair<iterator, bool> insert(const value_type &value) {
auto rv = data_insert(value);
return std::make_pair(iterator(rv.first), rv.second);
}
std::pair<iterator, bool> insert(value_type &&value) {
auto rv = data_insert(std::move(value));
return std::make_pair(iterator(rv.first), rv.second);
}
template <class InputIt>
void insert(InputIt first, InputIt second) {
for (; first != second; ++first) {
insert(*first);
}
}
void insert(std::initializer_list<value_type> ilist) {
insert(ilist.begin(), ilist.end());
}
template<class...Args>
std::pair<iterator, bool> emplace(Args&&... args) {
return insert(value_type(std::forward<Args>(args)...));
}
void erase(const_iterator pos) {
#if defined(SMALL_VECTOR_IS_STL_VECTOR)
// Cope with libstdc++ 4.8's incomplete STL (it's missing C++11
// vector::erase(const_iterator)) by explicitly using a non-const
// iterator.
auto pos_it = mutable_iterator(pos.get());
#else
auto pos_it = pos.get();
#endif
data().erase(pos_it);
}
void erase(const_iterator first, const_iterator last) {
#if defined(SMALL_VECTOR_IS_STL_VECTOR)
// As above, work around libstdc++ 4.8's incomplete C++11 support.
auto first_it = mutable_iterator(first.get());
auto last_it = mutable_iterator(last.get());
#else
auto first_it = first.get();
auto last_it = last.get();
#endif
data().erase(first_it, last_it);
}
void erase(const key_type &key) {
auto it = find(key);
if (it != end()) {
erase(it);
}
}
// Lookup.
size_type count(const key_type &key) const {
return find(key) != end() ? 1 : 0;
}
const_iterator find(const key_type &key) const {
return const_iterator(data_find(key));
}
// Element access.
mapped_type &at(const key_type &key) {
auto it = data_find(key);
if (it == data().end()) {
throw std::out_of_range("element not found");
}
return it->second;
}
const mapped_type &at(const key_type &key) const {
auto it = data_find(key);
if (it == data().end()) {
throw std::out_of_range("element not found");
}
return it->second;
}
mapped_type &operator[](const key_type &key) {
auto p = data_insert(value_type(key, mapped_type()));
return p.first->second;
}
// Observers.
class value_compare {
friend class flat_map;
protected:
Compare c;
value_compare(Compare c_in) : c(c_in) {}
public:
bool operator()(const value_type &lhs, const value_type &rhs) {
return c(lhs.first, rhs.first);
}
};
value_compare value_comp() const {
return value_compare(comp());
}
// Operators. All others provided by ue2::totally_ordered.
bool operator==(const flat_map &a) const {
return data() == a.data();
}
bool operator<(const flat_map &a) const {
return data() < a.data();
}
// Free swap function for ADL.
friend void swap(flat_map &a, flat_map &b) {
a.swap(b);
}
};
} // namespace ue2
namespace std {
template<typename T, typename Compare, typename Allocator>
struct hash<ue2::flat_set<T, Compare, Allocator>> {
size_t operator()(const ue2::flat_set<T, Compare, Allocator> &f) {
return ue2::ue2_hasher()(f);
}
};
template<typename Key, typename T, typename Compare, typename Allocator>
struct hash<ue2::flat_map<Key, T, Compare, Allocator>> {
size_t operator()(const ue2::flat_map<Key, T, Compare, Allocator> &f) {
return ue2::ue2_hasher()(f);
}
};
} // namespace std
#endif // UTIL_FLAT_CONTAINERS_H