Search code examples
c++setstdset

Is it possible to use elements of a different type than contained in a std::set to perform search and deletion?


Let's say I have the following:

struct MetadataThingy {

    void *actual_thingy;
    int some_metadata;
    int more_metadata;

    bool operator<(MetadataThingy const& other) const {
        return actual_thingy < other.actual_thingy;
    }

};

where actual_thingy points to some data of importance and I want the container ordered by the value of actual_thingy rather than the value of the element pointed at, but I need to store some other data about it, so I created the wrapper class MetadataThingy with a comparator that only considers the value of the actual_thingy pointer (rather than using a container of void *)

Now, given the following code:

std::set<MetadataThingy> thingy_set;

void test() {

    MetadataThingy m1 { nullptr, 5, 20 };
    MetadataThingy m2 { &m1, 1, 2 };
    MetadataThingy m3 { &m2, 6, 0 };

    thingy_set.insert(m1);
    thingy_set.insert(m2);
    thingy_set.insert(m3);

    MetadataThingy m;
    m = *thingy_set.find(m2); // OK.
    m = *thingy_set.find(static_cast<void *>(&m2)); // Nope. Can't use a pointer.

}

Since each MetadataThingy can be uniquely identified by the pointer value it stores and is ordered by the pointer value, it would make sense to find/delete objects simply by using a void * as the key. As it currently stands, though, I would have to create a dummy MetadataThingy each time I search for an element, which feels really kludgy. I've already considered using just a map with pointers as key and MetadataThingy as value but since each MetadataThingy must also contain the pointer anyway, this feels a bit redundant. So, is there a way to use an element of a type other than that stored in a set to find or delete values in the set, given that elements of the two types are mutually comparable and that elements of one type can be uniquely mapped into the other ( void * and MetadataThingy are isomorphic)? (I didn't include any in the above code, but suppose there are operator overloads for comparing void * and MetadataThingy in any order.)

A little background on the problem I'm trying to solve, just in case anyone can recommend a better approach: I need to order a collection by multiple criteria, so I have several MetadataThingy containers, all sorted by different criteria. "Metadata" in this case would be stuff I need to track the positions of the elements in all containers so that I can do fast removal. This would sound like a perfect job for boost multi-index containers, but the ordering of these elements is constantly changing, which AFAIK would mean it won't work.


Solution

  • As of C++14, std::set has templated versions of its lookup functions find, lower_bound, etc. They allow you to pass any object for comparison, as long as the comparer supports it.

    This means you can directly pass your void* to find, as long as the comparer supports comparing MetadataThingy and void*.

    For more information, see http://en.cppreference.com/w/cpp/container/set/find.

    To understand the limitation regarding Compare::is_transparent, I found this StackOverflow question very helpful.