Home > OS >  custom type_index order without boost
custom type_index order without boost

Time:04-26

I have the following map :

std::map<std::type_index, std::set<Status *>> statuses;

It store sets of the different subclasses of Status there, thanks to their std::type_index. However I want to have control about the order of the objects in this map (for instance, I have two classes Burn and Stun that inherit from Status, and I would like typeid(Stun) < typeif(Burn) so that when I iterate through the map I get the std::set<Stun> before the std::set<Burn>).

I thought about implementing a custom type_index class but I can't seem to find any way to do this without boost, which is a huge library that I would like not to have to include to my project.

Any advice on how to have a custom order in the map?

CodePudding user response:

You can wrap the type_index into a custom type with a comparator that yields the desired order.

The example from cppreference modified using std::map rather than std::unordered_map and with a custom comparator:

#include <iostream>
#include <typeinfo>
#include <typeindex>
#include <map>
#include <string>
#include <memory>
 
struct A {
    virtual ~A() {}
};
 
struct B : A {};
struct C : A {};

struct my_type_index {
    std::type_index index = std::type_index(typeid(void));
    my_type_index() = default;
    my_type_index(std::type_index i) : index(i) {}
    bool operator<(const my_type_index& other) const {
        static std::map< std::type_index,int> rank{
            { std::type_index(typeid(A)) , 1},
            { std::type_index(typeid(B)),  2},
            { std::type_index(typeid(double)),  3},
            { std::type_index(typeid(int)),  4},
            { std::type_index(typeid(C)),  5}
        };        
        return rank[index] < rank[other.index];
    }
};


int main()
{
    std::map<my_type_index, std::string> type_names;
 
    type_names[std::type_index(typeid(int))] = "int";
    type_names[std::type_index(typeid(double))] = "double";
    type_names[std::type_index(typeid(A))] = "A";
    type_names[std::type_index(typeid(B))] = "B";
    type_names[std::type_index(typeid(C))] = "C";
 
    int i;
    double d;
    A a;
 
    // note that we're storing pointer to type A
    std::unique_ptr<A> b(new B);
    std::unique_ptr<A> c(new C);
    for (const auto& e : type_names) {
        std::cout << e.first.index.name() << " " << e.second << "\n";
    }
}

output:

1A A
1B B
d double
i int
1C C

With improvements suggested by François Andrieux:

struct my_type_index {
    std::type_index index = std::type_index(typeid(void));
    int rank = 0;
    my_type_index() = default;
    my_type_index(std::type_index i) : index(i) {
        auto it = ranks.find(i);
        if (it != ranks.end()) rank = it->second;
    }
    
    static const std::map<std::type_index,int> ranks;
    
    bool operator<(const my_type_index& other) const {        
        return rank < other.rank;
    }
};

const std::map<std::type_index,int> my_type_index::ranks = [](){
    std::map<std::type_index,int> result;
    std::type_index index_order[] = {
        std::type_index(typeid(A)),
        std::type_index(typeid(B)),
        std::type_index(typeid(double)),
        std::type_index(typeid(int)),
        std::type_index(typeid(C)) 
    };
    for (size_t i = 0; i < std::size(index_order);  i){ result[ index_order[i]] = i 1; }
    return result;
}();

The ranking is stored in the static map ranks and each instances rank is already evaluated on construction (instead of on each comparison, ie comparisons are now cheaper). Also the map is now generated from an array, which is more robust (you cannot mistype the index/rank anymore). Further, the map is const, ie find instead of operator[] is used for look up. Elements not in the map will be assigned rank 0. Actually that was also the case before, but before operator[] was potentially adding unnecessary elements to the map.

  • Related