简体   繁体   中英

Can I use a map's iterator type as its mapped type?

I have a tree whose nodes are large strings. I don't really need to navigate the tree other than to follow the path from a node back to the root, so it suffices for each node to consist of the string and a pointer to its parent. I also need to be able to quickly find strings in the tree. The nodes of the tree themselves are not ordered, so this would require some sort of index. However, the strings are big enough that I would rather not duplicate them by storing them both in my tree and in my index.

I could implement both my tree and the index with a single std::map if the key for the map was the string and the mapped value was the pointer to its parent. However, I cannot figure out a way to write either of these types. My best guess would be something like this:

using Tree = std::map<std::string, typename Tree::const_iterator>;

or maybe:

using Node = std::pair<std::string const, typename Node const*>;
using Tree = std::map<std::string, Node const*>;

But these recursive type definitions don't compile. Is there any way to create this type in C++? Or a better way to do what I am trying to do?

You can wrap the iterator in a type of your own and reference that type instead to avoid the recurisve type problem.

struct const_iterator_wrapper {
    using iterator_type = map<string, const_iterator_wrapper>::const_iterator;

    iterator_type iter;

    const_iterator_wrapper() {}
    const_iterator_wrapper(iterator_type _iter) : iter(_iter) {}
};

using tree = map<string, const_iterator_wrapper>;

Your using Node definition doesn't compile, but an actual structure would:

struct Node {
    std::string const s;
    Node const* n;
};

It was not mentioned whether or not a node's parent might change after creation. If it does not, then a set might be a better fit than a map . (If it does change, then the set option is not completely off the table, but it might require weakening const correctness guarantees, possibly by making the parent pointer mutable .) In fact, you might not have to change your data structure much to use a set .

Let's say your nodes currently look like the following.

struct Node {
    std::string data;
    const Node * parent; // Might need to add `const`
};

You want these sorted by the data, ignoring the parent pointer. This might require defining a new function. If the following operator< is already defined as something else, then it takes a little more work to define your set , but still not hard.

bool operator<(const Node &a, const Node &b) {
    return a.data < b.data;
}

This is all you need to define a set of these nodes that will function much like your desired map.

std::set<Node> tree;

// Add a root element.
auto result = tree.emplace("root", nullptr);
auto root_it = result.first;

// Add a child to the root.
tree.emplace("child", &*root_it);
// The `&*` combination may look odd. It is, though, a way to
// convert an iterator to a pointer.

There are a few gotchas that some people might find unexpected, but nothing other than what comes from using a map for this role.

In the end, as far as the structure of the data is concerned, a map<K, const V> is equivalent to a set<pair<K, V>> with a suitable comparison function. While the member functions differ, the biggest real functional difference between a map and a set of pairs is that the map's values can be changed (hence const V instead of V earlier).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM