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