I have consulted many answers, but I still haven’t found the best way to implement it.
Now my case is to implement a multi map container, code below:
#include <unordered_map>
#include <utility>
static const size_t MAP_NUM_BITS = 5;
static const size_t MAP_NUM = (size_t)1 << MAP_NUM_BITS;
template<typename K, typename V>
class MultiMap {
using MapType = std::unordered_map<K, V>;
using MapIter = typename MapType::iterator;
using MapRef = typename MapType::reference;
public:
struct iterator {
MapIter it;
size_t idx;
MapType *multi_maps;
bool operator==(const iterator &rhs) const {
return it == rhs.it;
}
bool operator!=(const iterator &rhs) const {
return it != rhs.it;
}
iterator& operator++() {
++it;
while (it == multi_maps[idx].end() &&
idx + 1 < MAP_NUM) {
it = multi_maps[++idx].begin();
}
return *this;
}
iterator operator++(int) {
iterator ret = *this;
++*this;
return ret;
}
MapIter& operator->() {
return it;
}
MapRef operator*() {
return *it;
}
};
iterator begin() {
size_t idx = 0;
auto it = multi_maps_[idx].begin();
while (it == multi_maps_[idx].end() &&
idx + 1 < MAP_NUM) {
it = multi_maps_[++idx].begin();
}
return {it, idx, multi_maps_};
}
iterator end() {
return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
}
iterator find(const K &key) {
size_t hash = hasher_(key);
size_t idx = compute_idx(hash);
auto it = multi_maps_[idx].find(key);
if (it == multi_maps_[idx].end()) {
return end();
}
return {it, idx, multi_maps_};
}
std::pair<iterator, bool> insert(const std::pair<K, V> &data) {
size_t hash = hasher_(data.first);
size_t idx = compute_idx(hash);
auto res = multi_maps_[idx].insert(data);
return {{res.first, idx, multi_maps_}, res.second};
}
size_t size() const {
size_t total_size = 0;
for (auto &m : multi_maps_) {
total_size += m.size();
}
return total_size;
}
void clear() {
for (auto &m : multi_maps_) {
m.clear();
}
}
private:
size_t compute_idx(size_t hash) const {
if (MAP_NUM == 1) {
return 0;
} else {
return hash >> (sizeof(size_t) * 8 - MAP_NUM_BITS);
}
}
private:
MapType multi_maps_[MAP_NUM];
std::hash<K> hasher_;
};
Now it works in my test:
#include <iostream>
#include "multi_map.h"
int main() {
MultiMap<int, int> mm;
for (size_t i = 0; i < 5; ++i) {
mm.insert({2*i, i*i});
}
std::cout << "Map size is: " << mm.size() << std::endl;
for (size_t i = 0; i < 10; ++i) {
auto it = mm.find(i);
if (it != mm.end()) {
std::cout << "Found: " << i << std::endl;
} else {
std::cout << "Not found: " << i << std::endl;
}
}
for (auto it = mm.begin(); it != mm.end(); ++it) {
std::cout << it->first << " : " << it->second << std::endl;
}
return 0;
}
But when It refer to const type, thing goes bad!
So I have to implement a const_iterator, so the code is:
include <unordered_map>
#include <utility>
static const size_t MAP_NUM_BITS = 5;
static const size_t MAP_NUM = (size_t)1 << MAP_NUM_BITS;
template<typename K, typename V>
class MultiMap {
using MapType = std::unordered_map<K, V>;
using MapIter = typename MapType::iterator;
using MapConstIter = typename MapType::const_iterator;
using MapRef = typename MapType::reference;
using MapConstRef = typename MapType::const_reference;
public:
struct iterator {
MapIter it;
size_t idx;
MapType *multi_maps;
bool operator==(const iterator &rhs) const {
return it == rhs.it;
}
bool operator!=(const iterator &rhs) const {
return it != rhs.it;
}
iterator& operator++() {
++it;
while (it == multi_maps[idx].end() &&
idx + 1 < MAP_NUM) {
it = multi_maps[++idx].begin();
}
return *this;
}
iterator operator++(int) {
iterator ret = *this;
++*this;
return ret;
}
MapIter& operator->() {
return it;
}
MapRef operator*() {
return *it;
}
};
struct const_iterator {
MapConstIter it;
size_t idx;
const MapType *multi_maps;
bool operator==(const const_iterator &rhs) const {
return it == rhs.it;
}
bool operator!=(const const_iterator &rhs) const {
return it != rhs.it;
}
const_iterator& operator++() {
++it;
while (it == multi_maps[idx].end() &&
idx + 1 < MAP_NUM) {
it = multi_maps[++idx].begin();
}
return *this;
}
const_iterator operator++(int) {
const_iterator ret = *this;
++*this;
return ret;
}
MapConstIter& operator->() {
return it;
}
MapConstRef operator*() {
return *it;
}
};
iterator begin() {
size_t idx = 0;
auto it = multi_maps_[idx].begin();
while (it == multi_maps_[idx].end() &&
idx + 1 < MAP_NUM) {
it = multi_maps_[++idx].begin();
}
return {it, idx, multi_maps_};
}
const_iterator begin() const {
size_t idx = 0;
auto it = multi_maps_[idx].begin();
while (it == multi_maps_[idx].end() &&
idx + 1 < MAP_NUM) {
it = multi_maps_[++idx].begin();
}
return {it, idx, multi_maps_};
}
iterator end() {
return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
}
const_iterator end() const {
return {multi_maps_[MAP_NUM - 1].end(), MAP_NUM - 1, multi_maps_};
}
iterator find(const K &key) {
size_t hash = hasher_(key);
size_t idx = compute_idx(hash);
auto it = multi_maps_[idx].find(key);
if (it == multi_maps_[idx].end()) {
return end();
}
return {it, idx, multi_maps_};
}
const_iterator find(const K &key) const {
size_t hash = hasher_(key);
size_t idx = compute_idx(hash);
auto it = multi_maps_[idx].find(key);
if (it == multi_maps_[idx].end()) {
return end();
}
return {it, idx, multi_maps_};
}
std::pair<iterator, bool> insert(const std::pair<K, V> &data) {
size_t hash = hasher_(data.first);
size_t idx = compute_idx(hash);
auto res = multi_maps_[idx].insert(data);
return {{res.first, idx, multi_maps_}, res.second};
}
size_t size() const {
size_t total_size = 0;
for (auto &m : multi_maps_) {
total_size += m.size();
}
return total_size;
}
void clear() {
for (auto &m : multi_maps_) {
m.clear();
}
}
private:
size_t compute_idx(size_t hash) const {
if (MAP_NUM == 1) {
return 0;
} else {
return hash >> (sizeof(size_t) * 8 - MAP_NUM_BITS);
}
}
private:
MapType multi_maps_[MAP_NUM];
std::hash<K> hasher_;
};
The code duplication is unbearable.
For const and non-const function, const_cast will be help:
const_iterator begin() const {
return const_cast<MultiMap *>(*this)->begin();
}
To do that, I have to add a construct in const_iterator:
const_iterator(const iterator &it) {
this->it = it.it;
this->idx = it.idx;
this->multi_maps = it.multi_maps;
}
There is still too much code duplication here.
Is there a best solution to avoid code duplication in my case?
template<class Type> class iterator_impl;
andusing iterator = iterator_impl<T>;
using const_iterator = iterator_impl<const T>;
- If I think it's meaningful I make it so thatiterator
is convertible toconst_iterator
but not the other way around.const_iterator begin() const
, how can I convert the iterator return bynon-const begin()
to const_iterator?enable_if
d only for the caseconst_iterator(const interator&);
would be one option. If theiterator_impl
members are allpublic
you won't need afriend
relation, otherwise, you do.std::unordered_map<K, V>::iterator& operator->();
I would expect something likestd::unordered_map<K, V>::value_type* operator->();
It'll probably work because of the special behavior of->
but it does look a bit confusing to a reader :)