[英]Emulate std::map iterators for custom map that does not use tuples
I want to create a custom map that that actually uses a fixed set of keys, but should behave like a std::map
.我想创建一个自定义映射,它实际上使用一组固定的键,但应该表现得像
std::map
。 Basically I use an array internally and map the keys to indexes, allowing very efficient lookup.基本上我在内部使用一个数组并将键映射到索引,从而允许非常有效的查找。 I am however struggling to implement iterators that behave like
std::map
iterators, because I do not have internal std::pair
s that I can hand out references to.然而,我正在努力实现行为类似于
std::map
迭代器的迭代器,因为我没有可以std::pair
引用的内部std::pair
。
Is it possible to implement this as a zero-overhead abstraction while retaining the std::map
interface, in particular the iterators?是否可以在保留
std::map
接口(尤其是迭代器)的同时将其实现为零开销抽象?
The best i could come up with as operator*
is to return a rvalue std::pair<key_type, mapped_type*>
, which basically allows for the same operations, but unfortunately with different usage patterns.我能想到的最好的
operator*
是返回一个右值std::pair<key_type, mapped_type*>
,它基本上允许相同的操作,但不幸的是使用模式不同。
I have also tried std::pair<key_type, boost::referene_wrapper<mapped_type>>
, but that still doesn't allow for(auto& elem : map)
and often requires elem.second.get()
for reasons I do not understand.我也试过
std::pair<key_type, boost::referene_wrapper<mapped_type>>
,但仍然不允许for(auto& elem : map)
并且经常需要elem.second.get()
原因我不明白.
I am happy to use boost or lightweight header libraries, if there is anything that helps for the use case.如果对用例有任何帮助,我很乐意使用 boost 或轻量级头库。
To illustrate the case, here is a minimal example with a map that contains all letters 'a'-'z'.为了说明这种情况,下面是一个包含所有字母“a”-“z”的地图的最小示例。
using letter = char;
static const letter letter_begin = 'a';
static const letter letter_end = 'z' + 1;
template <typename T>
class letter_map
{
private:
using self = letter_map<T>;
template <typename IT, typename M>
class iterator_base : public std::iterator<std::input_iterator_tag, T>
{
public:
iterator_base(letter index, M& map) : index_(index), data_(map)
{
}
using self_iter = iterator_base<IT, M>;
IT operator*()
{
return IT(index_, &data_[index_]);
}
self_iter& operator++()
{
index_++;
return *this;
}
self_iter operator++(int)
{
self_iter tmp(*this);
operator++();
return tmp;
}
bool operator==(self_iter other) const
{
assert(&data_ == &other.data_);
return index_ == other.index_;
}
bool operator!=(self_iter other) const
{
return !(*this == other);
}
private:
letter index_;
M& data_;
};
public:
using key_type = letter;
using mapped_type = T;
using value_type = std::pair<const key_type, mapped_type*>;
using const_value_type = std::pair<const key_type, const mapped_type*>;
private:
static const size_t data_size = letter_end - letter_begin;
using container_type = std::array<mapped_type, data_size>;
public:
using iterator = iterator_base<value_type, self>;
using const_iterator = iterator_base<const_value_type, const self>;
public:
mapped_type& operator[](letter l)
{
return data_[l - letter_begin];
}
const mapped_type& operator[](letter l) const
{
return data_[l - letter_begin];
}
auto begin()
{
return iterator(letter_begin, *this);
}
auto end()
{
return iterator(letter_end, *this);
}
auto begin() const
{
return const_iterator(letter_begin, *this);
}
auto end() const
{
return const_iterator(letter_end, *this);
}
private:
container_type data_;
};
void print_string_letter_map(const letter_map<std::string>& lm)
{
for (auto elem : lm)
{
std::cout << elem.first << "->" << *(elem.second) << std::endl;
}
}
template<typename T>
class std_letter_map : public std::map<letter, T>
{
public:
std_letter_map()
{
for (letter l = letter_begin; l != letter_end; ++l) {
this->emplace(l, T());
}
}
};
void print_string_std_letter_map(const std_letter_map<std::string>& lm)
{
for (const auto& elem : lm)
{
std::cout << elem.first << "->" << elem.second << std::endl;
}
}
int main()
{
letter_map<std::string> lm;
// usually I would use auto& elem here
for (auto elem : lm) {
auto let = elem.first;
// usually this would be without the *
auto& str = *(elem.second);
str = std::string("foo ") + let;
}
print_string_letter_map(lm);
return 0;
}
Implementing operator *
is easy - just return std::pair<const Key, Value&>
or ..., Value const&>
for const iterator, like in this simplified example:实现
operator *
很容易 - 只需为 const 迭代器返回std::pair<const Key, Value&>
或..., Value const&>
,就像在这个简化的例子中一样:
template <typename T>
class iterator_array_as_map
{
public:
iterator_array_as_map(T* array, int index)
: array(array), index(index)
{}
bool operator == (const iterator_array_as_map& other) const
{
return index == other.index;
}
bool operator != (const iterator_array_as_map& other) const
{
return index != other.index;
}
iterator_array_as_map& operator ++ ()
{
++index;
return *this;
}
auto operator * ()
{
return std::pair<const int, T&>(index, array[index]);
}
private:
T* array;
int index;
};
And usage:和用法:
int main() {
int array[2] = {2, 4};
auto begin = iterator_array_as_map<int>(array, 0);
auto end = iterator_array_as_map<int>(array, 2);
for (auto it = begin; it != end; ++it)
{
std::cout << (*it).first << " " << (*it).second << std::endl;
}
(*begin).second = 7;
for (auto it = begin; it != end; ++it)
{
std::cout << (*it).first << " " << (*it).second << std::endl;
}
}
operator ->
is a little harder - since you must return something which needs to emulate pointer to std::pair
- but if do not mind about dynamic memory fragmentation - you can just return std::shared_ptr<std::pair<....>>
... operator ->
有点难 - 因为你必须返回一些需要模拟pointer to std::pair
- 但如果不介意动态内存碎片 - 你可以只返回std::shared_ptr<std::pair<....>>
...
auto operator -> ()
{
return std::make_shared<std::pair<const int, T&>>(index, array[index]);
}
If you do not want to use dynamic memory - then you might try with pointer to itself solution:如果您不想使用动态内存 - 那么您可以尝试使用指向自身解决方案的指针:
template<typename data>
class pointer_to_data
{
public:
template<typename ...Arg>
pointer_to_data(Arg&&... arg)
: data{std::forward<Arg>(arg)...}
{}
Data* operator -> ()
{
return &data;
}
private:
Data data;
};
Just return the above instead shared_ptr...只需返回上述内容,而不是 shared_ptr ...
See this what-is-the-correct-way-of-using-c11s-range-based-for , section "The special case of proxy iterators".请参阅此what-is-the-correct-way-of-using-c11s-range-based-for部分“代理迭代器的特殊情况”。 It is not possible to define
for(auto&e:b)
if b
is eg std::vector<bool>
- because in general it is not possible to have reference to temporary, and this very container is similar to yours in this sense that it has "Special" reference type.如果
b
是std::vector<bool>
,则无法定义for(auto&e:b)
- 因为通常不可能引用临时容器,并且这个容器在这个意义上与您的容器相似具有“特殊”引用类型。
You can try to have special member in your iterator keeping the "return value"- but that would be troublesome - so probably no better that this presented by me solution exist.您可以尝试在迭代器中使用特殊成员来保持“返回值”——但这会很麻烦——所以我提出的解决方案可能不会更好。
您可能可以使用 Eric Niebler 的范围库中的 zip_view https://github.com/ericniebler/range-v3
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.