/* *****************************************************************************
* kblib is a general utility library for C++14 and C++17, intended to provide
* performant high-level abstractions and more expressive ways to do simple
* things.
*
* Copyright (c) 2021 killerbee
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
* ****************************************************************************/
/**
* @file
* @brief Provides direct_map.
*
* @author killerbee
* @date 2019-2021
* @copyright GNU General Public Licence v3.0
*/
#ifndef DIRECT_MAP_H
#define DIRECT_MAP_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace KBLIB_NS {
/**
* @namespace detail_direct_map
* @internal
*/
namespace detail_direct_map {
template
KBLIB_CONSTANT auto range_of
= (std::numeric_limits::digits + std::numeric_limits::is_signed
< std::numeric_limits::digits)
? static_cast(1)
<< to_unsigned(std::numeric_limits::digits
+ std::numeric_limits::is_signed)
: 0;
static_assert(range_of == 1u << to_unsigned(CHAR_BIT), "");
static_assert(range_of == 1u << to_unsigned(CHAR_BIT), "");
template ::value and
std::is_trivially_destructible::value>
struct alignas(T) storage_for : private std::array {
template <
typename... Args,
enable_if_t::value, int> = 0>
constexpr auto construct(Args&&... args) noexcept(
std::is_nothrow_constructible::value) -> T& {
return *new (this->data()) T(std::forward(args)...);
}
storage_for() = default;
storage_for(const storage_for&) = delete;
storage_for(storage_for&&) = delete;
auto operator=(const storage_for&) -> storage_for& = delete;
auto operator=(storage_for&&) -> storage_for& = delete;
~storage_for() = default;
constexpr auto destroy() noexcept -> void { get()->~T(); }
#if __cpp_lib_launder
# define LAUNDER(x) std::launder(x)
#else
# define LAUNDER(x) (x)
#endif
KBLIB_NODISCARD constexpr auto get() & noexcept -> T* {
return LAUNDER(reinterpret_cast(this->data()));
}
KBLIB_NODISCARD constexpr auto get() const& noexcept -> const T* {
return LAUNDER(reinterpret_cast(this->data()));
}
#undef LAUNDER
};
template
struct alignas(T) storage_for {
private:
T t;
public:
template <
typename... Args,
enable_if_t::value, int> = 0>
constexpr auto construct(Args&&... args) noexcept(
std::is_nothrow_constructible::value) -> T& {
return *new (&t) T(std::forward(args)...);
}
constexpr auto destroy() noexcept -> void {}
KBLIB_NODISCARD constexpr auto get() & noexcept -> T* { return &t; }
KBLIB_NODISCARD constexpr auto get() const& noexcept -> const T* {
return &t;
}
};
} // namespace detail_direct_map
template
class direct_map {
// Allocating direct_map
public:
using key_type = Key;
using mapped_type = T;
using value_type = std::pair;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
private:
constexpr static std::ptrdiff_t key_range{detail_direct_map::range_of};
using storage_type = detail_direct_map::storage_for;
using held_type
= std::pair, std::array>;
template
class iter {
public:
copy_const_t* storage;
std::ptrdiff_t pos;
constexpr iter(decltype(storage) s, std::ptrdiff_t p)
: storage(s)
, pos(p) {
if (not storage) {
pos = max();
}
}
constexpr iter()
: iter(nullptr, max()) {}
using value_type = typename direct_map::value_type;
using difference_type = typename direct_map::difference_type;
using reference = copy_const_t&;
using pointer = copy_const_t*;
using iterator_category = std::bidirectional_iterator_tag;
KBLIB_NODISCARD constexpr auto operator*() const noexcept -> reference {
return *storage->second[uindex(pos)].get();
}
KBLIB_NODISCARD constexpr auto operator->() const noexcept -> pointer {
return storage->second[uindex(pos)].get();
}
constexpr auto operator++() noexcept -> iter& {
if (pos == max()) {
// not required in general, but direct_map::iterator guarantees that
// ++end() == end() because it simplifies the implementation and is
// unlikely to be a significant performance impact
return *this;
}
for (auto i : range(++pos, index(max()))) {
if (storage->first.test(uindex(i))) {
pos = i;
return *this;
}
}
pos = index(max());
return *this;
}
constexpr auto operator++(int) noexcept -> iter {
iter it = *this;
++*this;
return it;
}
constexpr auto operator--() noexcept -> iter& {
for (auto i : range(pos - 1, index(min()), -1)) {
if (storage->first.test(uindex(i))) {
pos = i;
return *this;
}
}
pos = index(min());
return *this;
}
constexpr auto operator--(int) noexcept -> iter {
iter it = *this;
--*this;
return it;
}
KBLIB_NODISCARD friend constexpr auto operator==(iter l, iter r) noexcept
-> bool {
return l.storage == r.storage and l.pos == r.pos;
}
KBLIB_NODISCARD friend constexpr auto operator!=(iter l, iter r) noexcept
-> bool {
return not (l == r);
}
#define DECL_OP(op) \
KBLIB_NODISCARD friend constexpr auto operator op(iter l, \
iter r) noexcept->bool { \
assert(l.storage == r.storage); \
return l.pos op r.pos; \
}
DECL_OP(<)
DECL_OP(>)
DECL_OP(>=)
DECL_OP(<=)
#undef DECL_OP
constexpr auto swap(iter& other) noexcept -> void {
kblib::swap(storage, other.storage);
kblib::swap(pos, other.pos);
}
};
public:
using iterator = iter;
using const_iterator = iter;
using reverse_iterator = std::reverse_iterator;
using const_reverse_iterator = std::reverse_iterator;
constexpr direct_map() noexcept = default;
template
constexpr direct_map(InputIt first, InputIt last)
: storage() {
if (first != last) {
allocate();
for (auto v : indirect(first, last)) {
construct(v.first, v.second);
}
}
}
// TODO(killerbee13): copy construction for allocating direct_map
constexpr direct_map(const direct_map& other)
: storage()
, _size(other._size) {
if (not other.empty()) {
allocate();
storage->first = other.storage->first;
for (auto k : range(+min(), max() + 1)) {
if (contains(k)) { // the bitmap is already copied from other
do_construct(k, other.at(k));
}
}
}
}
constexpr direct_map(direct_map&& other) noexcept
: storage(std::move(other.storage))
, _size(std::exchange(other._size, 0)) {}
constexpr direct_map(std::initializer_list init)
: direct_map(init.begin(), init.end()) {}
/**
*
*/
KBLIB_CXX20(constexpr) ~direct_map() { clear(); }
constexpr auto operator=(const direct_map& other) -> direct_map& {
if (this == &other) {
return *this;
}
clear();
storage.assign(in_place_agg, other.storage->first);
_size = other._size;
for (auto k : range(+min(), max() + 1)) {
if (contains(k)) { // the bitmap is already copied from other
do_construct(k, other.at(k));
bitmap().set(index(k));
}
}
return *this;
}
constexpr auto operator=(direct_map&& other) noexcept
-> direct_map& = default;
constexpr auto operator=(std::initializer_list init)
-> direct_map& {
clear();
for (auto it : init) {
construct(it->first, it->second);
}
return *this;
}
KBLIB_NODISCARD constexpr auto at(Key key) & -> T& {
if (contains(key)) {
return unsafe_at(key).get()->second;
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr auto at(Key key) && -> T&& {
if (contains(key)) {
return std::move(unsafe_at(key).get()->second);
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr auto at(Key key) const& -> const T& {
if (contains(key)) {
return unsafe_at(key).get()->second;
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr auto at(Key key) const&& -> const T&& {
if (contains(key)) {
return std::move(unsafe_at(key).get()->second);
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr T& operator[](Key key) noexcept(
std::is_nothrow_default_constructible::value) {
return try_emplace(key).first->second;
}
KBLIB_NODISCARD constexpr auto begin() & noexcept -> iterator {
return {storage.get(), cbegin().pos};
}
KBLIB_NODISCARD constexpr auto begin() const& noexcept -> const_iterator {
return {storage.get(), cbegin().pos};
}
KBLIB_NODISCARD constexpr auto cbegin() const& noexcept -> const_iterator {
if (not empty()) {
if (contains(to_key(min()))) {
return {storage.get(), min()};
} else {
return ++const_iterator{storage.get(), min()};
}
} else {
return end();
}
}
KBLIB_NODISCARD constexpr auto end() & noexcept -> iterator {
return {storage.get(), max()};
}
KBLIB_NODISCARD constexpr auto end() const& noexcept -> const_iterator {
return {storage.get(), max()};
}
KBLIB_NODISCARD constexpr auto cend() const& noexcept -> const_iterator {
return {storage.get(), max()};
}
KBLIB_NODISCARD constexpr auto rbegin() & noexcept -> auto {
return std::make_reverse_iterator(end());
}
KBLIB_NODISCARD constexpr auto rbegin() const& noexcept -> auto {
return std::make_reverse_iterator(end());
}
KBLIB_NODISCARD constexpr auto crbegin() const& noexcept -> auto {
return std::make_reverse_iterator(cend());
}
KBLIB_NODISCARD constexpr auto rend() & noexcept -> auto {
return std::make_reverse_iterator(begin());
}
KBLIB_NODISCARD constexpr auto rend() const& noexcept -> auto {
return std::make_reverse_iterator(begin());
}
KBLIB_NODISCARD constexpr auto crend() const& noexcept -> auto {
return std::make_reverse_iterator(cbegin());
}
KBLIB_NODISCARD constexpr auto empty() const& noexcept -> bool {
return _size == 0;
}
KBLIB_NODISCARD constexpr auto size() const& noexcept -> std::size_t {
return _size;
}
KBLIB_NODISCARD constexpr auto ssize() const& noexcept -> std::ptrdiff_t {
return _size;
}
KBLIB_NODISCARD constexpr static auto max_size() noexcept -> std::size_t {
return key_range;
}
constexpr auto clear() noexcept -> void {
if (_size == 0) {
return;
}
for (auto i : range(+min(), max() + 1)) {
auto j = static_cast(i);
if (contains(j)) {
unsafe_at(j).destroy();
}
}
storage.reset();
_size = 0;
}
constexpr auto insert(const value_type& value) -> std::pair {
if (not contains(value.first)) {
construct(value.first, value.second);
return {{storage.get(), index(value.first)}, true};
} else {
return {{storage.get(), index(value.first)}, false};
}
}
template
constexpr auto insert(U&& value)
-> enable_if_t::value,
std::pair> {
if (not contains(value.first)) {
construct(value.first, std::forward(value.second));
return {{storage.get(), index(value.first)}, true};
} else {
return {{storage.get(), index(value.first)}, false};
}
}
constexpr auto insert(value_type&& value) -> std::pair {
if (not contains(value.first)) {
construct(value.first, std::move(value.second));
return {{storage.get(), index(value.first)}, true};
} else {
return {{storage.get(), index(value.first)}, false};
}
}
template
constexpr auto insert_or_assign(Key key, U&& value)
-> std::pair {
if (not contains(key)) {
construct(key, std::forward(value));
return {{storage.get(), index(key)}, true};
} else {
*unsafe_at(key).get() = std::forward(value);
return {{storage.get(), index(key)}, false};
}
}
template
constexpr auto try_emplace(Key key, Args&&... args)
-> std::pair {
if (not contains(key)) {
construct(key, std::forward(args)...);
return {{storage.get(), index(key)}, true};
} else {
return {{storage.get(), index(key)}, false};
}
}
constexpr auto erase(iterator pos) noexcept -> iterator {
assert(contains(to_key(pos.pos)));
bitmap().reset(pos.pos);
unsafe_at(to_key(pos.pos)).destroy();
--_size;
return ++pos;
}
constexpr auto erase(const_iterator pos) noexcept -> iterator {
assert(contains(to_key(pos.pos)));
bitmap().reset(pos.pos);
unsafe_at(to_key(pos.pos)).destroy();
--_size;
return ++pos;
}
constexpr auto erase(iterator first, iterator last) noexcept -> iterator {
for (auto i : range(first.pos, last.pos)) {
if (contains(to_key(i))) {
bitmap().reset(i);
unsafe_at(to_key(i)).destroy();
--_size;
}
}
return ++last;
}
constexpr auto erase(Key key) noexcept -> std::size_t {
if (contains(key)) {
bitmap().reset(index(key));
unsafe_at(key).destroy();
--_size;
return 1;
} else {
return 0;
}
}
constexpr auto swap(direct_map& other) noexcept -> void {
using std::swap;
swap(storage, other.storage);
swap(_size, other._size);
}
KBLIB_NODISCARD constexpr auto contains(Key key) const noexcept -> bool {
return storage and bitmap().test(uindex(key));
}
KBLIB_NODISCARD constexpr auto count(Key key) const noexcept -> std::size_t {
return contains(key);
}
KBLIB_NODISCARD constexpr auto find(Key key) & noexcept -> iterator {
return contains(key) ? iterator{storage.get(), index(key)} : end();
}
KBLIB_NODISCARD constexpr auto find(Key key) const& noexcept
-> const_iterator {
return contains(key) ? iterator{storage.get(), index(key)} : end();
}
KBLIB_NODISCARD constexpr auto equal_range(Key key) & noexcept
-> std::pair {
return {lower_bound(key), upper_bound(key)};
}
KBLIB_NODISCARD constexpr auto equal_range(Key key) const& noexcept
-> std::pair {
return {lower_bound(key), upper_bound(key)};
}
KBLIB_NODISCARD constexpr auto lower_bound(Key key) & noexcept -> iterator {
if (contains(key)) {
return find(key);
} else {
return ++iterator{storage.get(), index(key)};
}
}
KBLIB_NODISCARD constexpr auto lower_bound(Key key) const& noexcept
-> const_iterator {
if (contains(key)) {
return find(key);
} else {
return ++iterator{storage.get(), index(key)};
}
}
KBLIB_NODISCARD constexpr auto upper_bound(Key key) & noexcept -> iterator {
// if the key exists, upper_bound is the next one
auto l = lower_bound(key);
if (l.pos == index(key)) {
return ++l;
// otherwise upper_bound == lower_bound
} else {
return l;
}
}
KBLIB_NODISCARD constexpr auto upper_bound(Key key) const& noexcept
-> const_iterator {
// if the key exists, upper_bound is the next one
auto l = lower_bound(key);
if (l.pos == index(key)) {
return ++l;
// otherwise upper_bound == lower_bound
} else {
return l;
}
}
KBLIB_NODISCARD constexpr static auto min() noexcept -> Key {
return std::numeric_limits::min();
}
KBLIB_NODISCARD constexpr static auto max() noexcept -> Key {
return std::numeric_limits::max();
}
KBLIB_NODISCARD friend constexpr auto operator==(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval()
== std::declval())) -> bool {
if (l.size() != r.size()) {
return false;
}
for (auto i : kblib::range(+min(), max() + 1)) {
if (l.contains(i) != r.contains(i)) {
return false;
} else if (l.contains(i)) {
if (l.at(i) != r.at(i)) {
return false;
}
}
}
return true;
}
KBLIB_NODISCARD friend constexpr auto operator!=(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval()
== std::declval())) -> bool {
return not (l == r);
}
KBLIB_NODISCARD friend constexpr auto operator<(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return kblib::lexicographical_compare(l.begin(), l.end(), r.begin(),
r.end());
}
KBLIB_NODISCARD friend constexpr auto operator>(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return r < l;
}
KBLIB_NODISCARD friend constexpr auto operator<=(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return not (r < l);
}
KBLIB_NODISCARD friend constexpr auto operator>=(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return not (l < r);
}
KBLIB_NODISCARD constexpr static auto index(Key key) noexcept
-> std::ptrdiff_t {
return to_signed(key);
}
KBLIB_NODISCARD constexpr static auto uindex(Key key) noexcept
-> std::size_t {
return to_unsigned(key);
}
KBLIB_NODISCARD constexpr static auto to_key(std::ptrdiff_t i) noexcept
-> Key {
return Key(i);
}
private:
KBLIB_NODISCARD constexpr auto bitmap() noexcept -> std::bitset& {
return storage->first;
}
KBLIB_NODISCARD constexpr const std::bitset& bitmap()
const noexcept {
return storage->first;
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) & -> storage_type& {
return storage->second[uindex(key)];
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) && -> storage_type&& {
return storage->second[uindex(key)];
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) const& -> const
storage_type& {
return storage->second[uindex(key)];
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) const&& -> const
storage_type&& {
return storage->second[uindex(key)];
}
auto allocate() -> void {
if (not storage) {
storage.assign();
}
}
template
constexpr void construct(Key key, Args&&... args) noexcept(
std::is_nothrow_constructible::value) {
allocate();
if (not storage->first.test(uindex(key))) {
do_construct(key, std::forward(args)...);
// doing these after construction maintains exception safety.
storage->first.set(uindex(key));
++_size;
}
}
template
constexpr void do_construct(Key key, Args&&... args) noexcept(
std::is_nothrow_constructible::value) {
storage->second[uindex(key)].construct(
std::piecewise_construct, std::forward_as_tuple(key),
std::forward_as_tuple(std::forward(args)...));
}
// TODO(killerbee13): Implement, test, and document direct_map
// TODO(killerbee13): allocator support for direct_map
kblib::heap_value storage;
std::size_t _size{};
};
template
class direct_map {
// Non-allocating direct_map
public:
using key_type = Key;
using mapped_type = T;
using value_type = std::pair;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
private:
constexpr static std::ptrdiff_t key_range{detail_direct_map::range_of};
using storage_type = detail_direct_map::storage_for;
template
class iter {
public:
copy_const_t* map;
std::ptrdiff_t pos;
constexpr iter(decltype(map) s, std::ptrdiff_t p)
: map(s)
, pos(p) {
if (not map) {
pos = max();
}
}
constexpr iter()
: iter(nullptr, max()) {}
using value_type = typename direct_map::value_type;
using difference_type = typename direct_map::difference_type;
using reference = copy_const_t&;
using pointer = copy_const_t*;
using iterator_category = std::bidirectional_iterator_tag;
KBLIB_NODISCARD constexpr auto operator*() const -> reference {
return *map->elems[uindex(pos)].get();
}
KBLIB_NODISCARD constexpr auto operator->() const -> pointer {
return map->elems[uindex(pos)].get();
}
constexpr auto operator++() -> iter& {
if (pos == max()) {
// not required in general, but direct_map::iterator guarantees that
// ++end() == end() because it simplifies the implementation and is
// unlikely to be a significant performance impact
return *this;
}
for (auto i : range(++pos, index(max()))) {
if (map->active_elems.test(uindex(i))) {
pos = i;
return *this;
}
}
pos = index(max());
return *this;
}
constexpr auto operator++(int) -> iter {
iter it = *this;
++*this;
return it;
}
constexpr auto operator--() -> iter& {
for (auto i : range(pos - 1, index(min()), -1)) {
if (map->active_elems.test(uindex(i))) {
pos = i;
return *this;
}
}
pos = index(min());
return *this;
}
constexpr auto operator--(int) -> iter {
iter it = *this;
--*this;
return it;
}
KBLIB_NODISCARD friend constexpr auto operator==(iter l, iter r) noexcept
-> bool {
return l.map == r.map and l.pos == r.pos;
}
KBLIB_NODISCARD friend constexpr auto operator!=(iter l, iter r) noexcept
-> bool {
return not (l == r);
}
#define DECL_OP(op) \
KBLIB_NODISCARD friend constexpr auto operator op(iter l, \
iter r) noexcept->bool { \
assert(l.map == r.map); \
return l.pos op r.pos; \
}
DECL_OP(<)
DECL_OP(>)
DECL_OP(>=)
DECL_OP(<=)
#undef DECL_OP
constexpr auto swap(iter& other) noexcept -> void {
kblib::swap(map, other.map);
kblib::swap(pos, other.pos);
}
};
public:
using iterator = iter;
using const_iterator = iter;
using reverse_iterator = std::reverse_iterator;
using const_reverse_iterator = std::reverse_iterator;
constexpr direct_map() noexcept = default;
template
constexpr direct_map(InputIt first, InputIt last) {
for (auto&& v : indirect(first, last)) {
construct(std::forward(v).first,
std::forward(v).second);
}
}
constexpr direct_map(const direct_map& other)
: active_elems{other.active_elems}
, _size(other._size) {
for (const key_type k : range(+min(), max() + 1)) {
if (contains(k)) { // the bitmap is already copied from other
do_construct(k, other.unsafe_at(k).get()->second);
bitmap().set(uindex(k));
}
}
}
constexpr direct_map(direct_map&& other) noexcept(
std::is_nothrow_move_constructible::value)
: active_elems{other.active_elems}
, _size(other._size) {
for (const key_type k : range(+min(), max() + 1)) {
if (contains(k)) { // the bitmap is already copied from other
do_construct(k, std::move(other.unsafe_at(k).get()->second));
bitmap().set(uindex(k));
}
}
}
constexpr direct_map(std::initializer_list init)
: direct_map(init.begin(), init.end()) {}
// TODO(killerbee13): Remove this and allow it to be trivially destructible
// for trivial Key and T
KBLIB_CXX20(constexpr) ~direct_map() { clear(); }
constexpr auto operator=(const direct_map& other) -> direct_map& {
if (this == &other) {
return *this;
}
clear();
active_elems = other.active_elems;
_size = other._size;
for (const key_type k : range(+min(), max() + 1)) {
if (contains(k)) { // the bitmap is already copied from other
do_construct(k, other.unsafe_at(k).get()->second);
bitmap().set(uindex(k));
}
}
return *this;
}
constexpr auto operator=(direct_map&& other) noexcept(
std::is_nothrow_move_constructible::value) -> direct_map& {
if (this == &other) {
return *this;
}
active_elems = other.active_elems;
_size = other._size;
for (const key_type k : range(+min(), max() + 1)) {
if (contains(k)) { // the bitmap is already copied from other
do_construct(k, std::move(other.unsafe_at(k).get()->second));
bitmap().set(uindex(k));
}
}
return *this;
}
constexpr auto operator=(std::initializer_list init)
-> direct_map& {
clear();
for (auto it : init) {
construct(it->first, it->second);
}
return *this;
}
KBLIB_NODISCARD constexpr auto at(Key key) & -> T& {
if (contains(key)) {
return unsafe_at(key).get()->second;
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr auto at(Key key) && -> T&& {
if (contains(key)) {
return std::move(unsafe_at(key).get()->second);
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr auto at(Key key) const& -> const T& {
if (contains(key)) {
return unsafe_at(key).get()->second;
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr auto at(Key key) const&& -> const T&& {
if (contains(key)) {
return std::move(unsafe_at(key).get()->second);
} else {
throw std::out_of_range("direct_map: key out of range");
}
}
KBLIB_NODISCARD constexpr T& operator[](Key key) noexcept(
std::is_nothrow_default_constructible::value) {
return try_emplace(key).first->second;
}
KBLIB_NODISCARD constexpr auto begin() & noexcept -> iterator {
return {this, cbegin().pos};
}
KBLIB_NODISCARD constexpr auto begin() const& noexcept -> const_iterator {
return {this, cbegin().pos};
}
KBLIB_NODISCARD constexpr auto cbegin() const& noexcept -> const_iterator {
if (not empty()) {
if (contains(to_key(min()))) {
return {this, min()};
} else {
return ++const_iterator{this, min()};
}
} else {
return end();
}
}
KBLIB_NODISCARD constexpr auto end() & noexcept -> iterator {
return {this, max()};
}
KBLIB_NODISCARD constexpr auto end() const& noexcept -> const_iterator {
return {this, max()};
}
KBLIB_NODISCARD constexpr auto cend() const& noexcept -> const_iterator {
return {this, max()};
}
KBLIB_NODISCARD constexpr auto rbegin() & noexcept -> reverse_iterator {
return std::make_reverse_iterator(end());
}
KBLIB_NODISCARD constexpr auto rbegin() const& noexcept
-> const_reverse_iterator {
return std::make_reverse_iterator(end());
}
KBLIB_NODISCARD constexpr auto crbegin() const& noexcept
-> const_reverse_iterator {
return std::make_reverse_iterator(crend());
}
KBLIB_NODISCARD constexpr auto rend() & noexcept -> reverse_iterator {
return std::make_reverse_iterator(begin());
}
KBLIB_NODISCARD constexpr auto rend() const& noexcept
-> const_reverse_iterator {
return std::make_reverse_iterator(begin());
}
KBLIB_NODISCARD constexpr auto crend() const& noexcept
-> const_reverse_iterator {
return std::make_reverse_iterator(cbegin());
}
KBLIB_NODISCARD constexpr auto empty() const& noexcept -> bool {
return _size == 0;
}
KBLIB_NODISCARD constexpr auto size() const& noexcept -> std::size_t {
return _size;
}
KBLIB_NODISCARD constexpr auto ssize() const& noexcept -> std::ptrdiff_t {
return _size;
}
KBLIB_NODISCARD constexpr static auto max_size() noexcept -> std::size_t {
return key_range;
}
constexpr auto clear() noexcept -> void {
for (auto i : range(+min(), max() + 1)) {
auto j = static_cast(i);
if (contains(j)) {
unsafe_at(j).destroy();
}
}
_size = 0;
}
constexpr auto insert(const value_type& value) -> std::pair {
if (not contains(value.first)) {
construct(value.first, value.second);
return {{this, index(value.first)}, true};
} else {
return {{this, index(value.first)}, false};
}
}
template
constexpr auto insert(U&& value)
-> return_assert_t::value,
std::pair> {
if (not contains(value.first)) {
construct(value.first, std::forward(value.second));
return {{this, index(value.first)}, true};
} else {
return {{this, index(value.first)}, false};
}
}
constexpr auto insert(value_type&& value) -> std::pair {
if (not contains(value.first)) {
construct(value.first, std::move(value.second));
return {{this, index(value.first)}, true};
} else {
return {{this, index(value.first)}, false};
}
}
template
constexpr auto insert_or_assign(Key key, U&& value)
-> std::pair {
if (not contains(key)) {
construct(key, std::forward(value));
return {{this, index(key)}, true};
} else {
*unsafe_at(key).get() = std::forward(value);
return {{this, index(key)}, false};
}
}
template
constexpr auto try_emplace(Key key, Args&&... args)
-> std::pair {
if (not contains(key)) {
construct(key, std::forward(args)...);
return {{this, index(key)}, true};
} else {
return {{this, index(key)}, false};
}
}
constexpr auto erase(iterator pos) noexcept -> iterator {
assert(contains(to_key(pos.pos)));
destroy(to_key(pos.pos));
return ++pos;
}
constexpr auto erase(const_iterator pos) noexcept -> iterator {
assert(contains(to_key(pos.pos)));
destroy(to_key(pos.pos));
return ++pos;
}
constexpr auto erase(iterator first, iterator last) noexcept -> iterator {
for (auto i : range(first.pos, last.pos)) {
if (contains(to_key(i))) {
destroy(to_key(i));
}
}
return ++last;
}
constexpr auto erase(Key key) noexcept -> std::size_t {
if (contains(key)) {
destroy(key);
return 1;
} else {
return 0;
}
}
constexpr auto swap(direct_map& other) noexcept(
std::is_nothrow_move_constructible::value and
fakestd::is_nothrow_swappable::value) -> void {
using std::swap;
for (const key_type k : range(+min(), max() + 1)) {
if (contains(k)) {
if (other.contains(k)) {
kblib::swap(unsafe_at(k).get()->second,
other.unsafe_at(k).get()->second);
} else {
other.construct(k, std::move(unsafe_at(k).get()->second));
destroy(k);
}
} else if (other.contains(k)) {
construct(k, std::move(other.unsafe_at(k).get()->second));
other.destroy(k);
} else {
// do nothing
}
}
swap(active_elems, other.active_elems);
swap(_size, other._size);
}
KBLIB_NODISCARD constexpr auto contains(Key key) const noexcept -> bool {
return bitmap().test(uindex(key));
}
KBLIB_NODISCARD constexpr auto count(Key key) const noexcept -> std::size_t {
return contains(key);
}
KBLIB_NODISCARD constexpr auto find(Key key) & noexcept -> iterator {
return contains(key) ? iterator{this, index(key)} : end();
}
KBLIB_NODISCARD constexpr auto find(Key key) const& noexcept
-> const_iterator {
return contains(key) ? iterator{this, index(key)} : end();
}
KBLIB_NODISCARD constexpr auto equal_range(Key key) & noexcept
-> std::pair {
return {lower_bound(key), upper_bound(key)};
}
KBLIB_NODISCARD constexpr auto equal_range(Key key) const& noexcept
-> std::pair {
return {lower_bound(key), upper_bound(key)};
}
KBLIB_NODISCARD constexpr auto lower_bound(Key key) & noexcept -> iterator {
if (contains(key)) {
return find(key);
} else {
return ++iterator{this, index(key)};
}
}
KBLIB_NODISCARD constexpr auto lower_bound(Key key) const& noexcept
-> const_iterator {
if (contains(key)) {
return find(key);
} else {
return ++iterator{this, index(key)};
}
}
KBLIB_NODISCARD constexpr auto upper_bound(Key key) & noexcept -> iterator {
// if the key exists, upper_bound is the next one
auto l = lower_bound(key);
if (l.pos == index(key)) {
return ++l;
// otherwise upper_bound == lower_bound
} else {
return l;
}
}
KBLIB_NODISCARD constexpr auto upper_bound(Key key) const& noexcept
-> const_iterator {
// if the key exists, upper_bound is the next one
auto l = lower_bound(key);
if (l.pos == index(key)) {
return ++l;
// otherwise upper_bound == lower_bound
} else {
return l;
}
}
KBLIB_NODISCARD constexpr static auto min() noexcept -> Key {
return std::numeric_limits::min();
}
KBLIB_NODISCARD constexpr static auto max() noexcept -> Key {
return std::numeric_limits::max();
}
KBLIB_NODISCARD friend constexpr auto operator==(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval()
== std::declval())) -> bool {
if (l.size() != r.size()) {
return false;
}
for (const key_type i : kblib::range(+min(), max() + 1)) {
if (l.contains(i) != r.contains(i)) {
return false;
} else if (l.contains(i)) {
if (l.at(i) != r.at(i)) {
return false;
}
}
}
return true;
}
KBLIB_NODISCARD friend constexpr auto operator!=(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval()
== std::declval())) -> bool {
return not (l == r);
}
KBLIB_NODISCARD friend constexpr auto operator<(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return kblib::lexicographical_compare(l.begin(), l.end(), r.begin(),
r.end());
}
KBLIB_NODISCARD friend constexpr auto operator>(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return r < l;
}
KBLIB_NODISCARD friend constexpr auto operator<=(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return not (r < l);
}
KBLIB_NODISCARD friend constexpr auto operator>=(
const direct_map& l,
const direct_map& r) noexcept(noexcept(std::declval(),
std::declval())) -> bool {
return not (l < r);
}
KBLIB_NODISCARD constexpr static auto index(Key key) noexcept
-> std::ptrdiff_t {
return to_signed(key);
}
KBLIB_NODISCARD constexpr static auto uindex(Key key) noexcept
-> std::size_t {
return to_unsigned(key);
}
KBLIB_NODISCARD constexpr static auto to_key(std::ptrdiff_t i) noexcept
-> Key {
return Key(i);
}
private:
KBLIB_NODISCARD constexpr auto bitmap() noexcept -> std::bitset& {
return active_elems;
}
KBLIB_NODISCARD constexpr auto bitmap() const noexcept
-> const std::bitset& {
return active_elems;
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) & -> storage_type& {
return elems[uindex(key)];
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) && -> storage_type&& {
return elems[uindex(key)];
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) const& -> const
storage_type& {
return elems[uindex(key)];
}
KBLIB_NODISCARD constexpr auto unsafe_at(Key key) const&& -> const
storage_type&& {
return elems[uindex(key)];
}
template
constexpr auto construct(Key key, Args&&... args) noexcept(
std::is_nothrow_constructible::value) -> void {
if (not active_elems.test(uindex(key))) {
do_construct(key, std::forward(args)...);
// doing these after construction maintains exception safety.
active_elems.set(uindex(key));
++_size;
}
}
template
constexpr auto do_construct(Key key, Args&&... args) noexcept(
std::is_nothrow_constructible::value) -> void {
elems[uindex(key)].construct(
std::piecewise_construct, std::forward_as_tuple(key),
std::forward_as_tuple(std::forward(args)...));
}
auto destroy(Key key) -> void {
assert(contains(key));
bitmap().reset(uindex(key));
unsafe_at(key).destroy();
--_size;
}
// TODO(killerbee13): Implement, test, and document direct_map
std::bitset active_elems;
std::array elems;
std::size_t _size{};
};
} // namespace KBLIB_NS
#endif // DIRECT_MAP_H