【问题标题】:C++ map -- Self referencing iteratorC++ map -- 自引用迭代器
【发布时间】:2019-02-21 13:00:13
【问题描述】:

有没有办法声明一个std::map,它的值类型是一个自身的迭代器?

map<string, map<string, (#)>::iterator> myMap;

上面的代码 sn-p 不起作用,因为迭代器类型需要知道第二个模板参数,标记为(#)。 (它本身就是)。

目的是避免执行不必要的find 操作来访问另一个元素指向的元素——而不是使用map&lt;string, string&gt;

【问题讨论】:

  • 只是为了好奇,你想用这个来达到什么目的?
  • 不,这是不可能的。这种映射的迭代器的定义将是无限递归的。 也许使用一些间接技巧,这可以有效地模拟。
  • @MichaelJ 一般而言,eerorika said in their answer(指向彼此的元素的映射)
  • 它基本上相当于std::map&lt;string,string&gt;,唯一的区别是找到一个值的键字符串是对数而不是常数,我不知道你想做什么,但我建议考虑使用比map&lt;string,string&gt; 更复杂的东西是否值得努力

标签: c++ iterator self-reference


【解决方案1】:

这样的定义是不可能的,因为值类型和迭代器类型将相互无限递归。

可以通过一些间接的方式来解决这个问题。甚至可以避免std::any 的动态分配,以及std::map&lt;K,V&gt; 未定义的事实,除非V 完整。

但解决方案有点棘手,并且依赖于一些合理的假设,但标准并未指定。请参阅实现中的 cmets。主要技巧是将成员变量类型的定义推迟到封装类的定义之后。这是通过重用原始存储来实现的。

先用:

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")));
}

定义

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);
}

旧答案;这假设在遍历存储的映射时避免查找并不是一项重要功能。

您尝试表示的数据结构似乎是一组键(字符串),其中每个键映射到该组的另一个键。更简单的表示方法是将这两个方面分开:

using Set = std::set<std::string>;
using Map = std::map<Set::iterator, Set::iterator>;

请注意,这两种数据结构不会自动保持同步。添加到集合中的元素不会自动映射到另一个元素,从集合中删除的元素会在映射中留下悬空迭代器。因此,明智的做法是编写一个自定义容器类来强制执行必要的不变量。

【讨论】:

  • 使用std::map&lt;string, Set::iterator&gt;不是更好吗?否则,要找到某个元素的“匹配”,首先必须在集合中find 它,然后在映射中find 对应的迭代器。但作为一种妥协,会有重复的字符串......
  • @Anakhand 取决于您要执行的操作类型。另一方面,为什么不只是std::map&lt;std::string,std::string&gt;
  • 我最初确实使用了map&lt;string, string&gt;,但我想看看是否可以通过避免不必要的find 操作(即访问另一个元素指向的元素)来提高性能。但是我猜对数时间如果能简化程序的话也不算太糟糕……
  • @Anakhand 我认为这是可能的(正如我在对问题的评论中提到的那样),但它需要一些聪明的东西,并且需要大量的工作。有空我会调查的。
  • @Anakhand 我写了一个概念证明。它可能包含错误。
【解决方案2】:

仅通过类型擦除。例如,您可以使用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"]);

编辑:以下似乎也有效(clang-7.0.0 和 gcc-8.2),但 it is illegal(基本上 std::map 没有指定允许不完整的类型):

struct Iter;
using Map = std::map<std::string, Iter>;
struct Iter {
    Map::iterator it;
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    • 1970-01-01
    • 2014-05-27
    • 1970-01-01
    • 1970-01-01
    • 2017-10-08
    相关资源
    最近更新 更多