C++ map — Self referencing iterator

前端 未结 2 1225
萌比男神i
萌比男神i 2021-01-20 01:16

Is there a way to declare a std::map whose value type is an iterator to itself?

map::iterator> myMap;


        
相关标签:
2条回答
  • 2021-01-20 01:23

    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<K,V> 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<int, int>::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<std::string, iter_wrap>;
    using It = Map::iterator;
    
    struct conv {
        static constexpr It&
        it(iter_wrap&& wrap) noexcept {
            return *std::launder(reinterpret_cast<It*>(wrap.store));
        }
        static constexpr const It&
        it(const iter_wrap& wrap) noexcept {
            return *std::launder(reinterpret_cast<const It*>(wrap.store));
        }
        template<class It>
        static const iter_wrap
        wrap(It&& it) {
            iter_wrap iw(noInitTag);
            create(iw, std::forward<It>(it));
            return iw;
        }
        template<class... Args>
        static void
        create(iter_wrap& wrap, Args&&... args) {
            new(wrap.store) It(std::forward<Args>(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<std::string>;
    using Map = std::map<Set::iterator, Set::iterator>;
    

    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.

    0 讨论(0)
  • 2021-01-20 01:30

    Only through type erasure. For example, you could use std::any

    std::map<std::string, std::any> myMap;
    auto inserted = myMap.emplace("foo", std::any());
    
    // how it can be populated:
    inserted.first->second = inserted.first;
    using it_type = decltype(myMap.begin());
    
    // how values can be extracted:
    auto it = std::any_cast<it_type>(myMap["foo"]);
    

    EDIT: The following also seems to work (clang-7.0.0 and gcc-8.2), but it is illegal (basically std::map does not specify that incomplete types are allowed):

    struct Iter;
    using Map = std::map<std::string, Iter>;
    struct Iter {
        Map::iterator it;
    };
    
    0 讨论(0)
提交回复
热议问题