Is there a way to declare a std::map
whose value type is an iterator to itself?
map::iterator> myMap;
Such definition is not possible, since the value type and the iterator type would be mutually infinitely recursive.
It is possible to work around this using a bit of indirection. It is even possible to avoid the dynamic allocation of std::any
, and the fact that std::map
is undefined unless V
is complete.
But the solution is a bit tricky, and relies on some assumptions which are reasonable, but not specified by the standard. See comments in the implementation. The main trick is to defer definition of a member variable type until after definition of the enveloping class. This is achieved by reusing raw storage.
Usage first:
int main()
{
Map map;
auto [it, _] = map.emplace("first", iter_wrap{});
map.emplace("maps to first", conv::wrap(it));
// erase first mapping by only looking
// up the element that maps to it
map.erase(conv::it(map.find("maps to first")));
}
Definition
struct NoInitTag {} noInitTag;
class iter_wrap
{
public:
iter_wrap();
~iter_wrap();
iter_wrap(const iter_wrap&);
iter_wrap(iter_wrap&&);
const iter_wrap& operator=(const iter_wrap&);
const iter_wrap& operator=(iter_wrap&&);
private:
// We rely on assumption that all map iterators have the same size and alignment.
// Compiler should hopefully warn if our allocation is insufficient.
using dummy_it = std::map::iterator;
static constexpr auto it_size = sizeof(dummy_it);
static constexpr auto it_align = alignof(dummy_it);
alignas(it_align) std::byte store[it_size];
explicit iter_wrap(NoInitTag){}
friend struct conv;
};
using Map = std::map;
using It = Map::iterator;
struct conv {
static constexpr It&
it(iter_wrap&& wrap) noexcept {
return *std::launder(reinterpret_cast(wrap.store));
}
static constexpr const It&
it(const iter_wrap& wrap) noexcept {
return *std::launder(reinterpret_cast(wrap.store));
}
template
static const iter_wrap
wrap(It&& it) {
iter_wrap iw(noInitTag);
create(iw, std::forward(it));
return iw;
}
template
static void
create(iter_wrap& wrap, Args&&... args) {
new(wrap.store) It(std::forward(args)...);
}
static constexpr void
destroy(iter_wrap& wrap) {
it(wrap).~It();
}
};
iter_wrap::iter_wrap() {
conv::create(*this);
}
iter_wrap::iter_wrap(const iter_wrap& other) {
conv::create(*this, conv::it(other));
}
iter_wrap::iter_wrap(iter_wrap&& other) {
conv::create(*this, std::move(conv::it(other)));
}
const iter_wrap& iter_wrap::operator=(const iter_wrap& other) {
conv::destroy(*this);
conv::create(*this, conv::it(other));
return *this;
}
const iter_wrap& iter_wrap::operator=(iter_wrap&& other) {
conv::destroy(*this);
conv::create(*this, std::move(conv::it(other)));
return *this;
}
iter_wrap::~iter_wrap() {
conv::destroy(*this);
}
Old answer; This assumed that it was not an important feature to avoid lookups while traversing stored mappings.
It appears that the data structure that you attempt to represent is a set of keys (strings), where each key maps to another key of the set. Easier way to represent that is to separate those two aspects:
using Set = std::set;
using Map = std::map;
Note that these two data structures do not automatically stay in sync. An element added to the set doesn't automatically have a mapping to another, and an element erased from the set leaves dangling iterators to the map. As such, it would be wise to write a custom container class that enforces the necessary invariants.