My approach would be to use enum class. This allows to have typesafe identifier, that is char or int underneath. A simple code example would be:
#include <iostream>
enum class COLOR {
RED
, GREEN
};
std::ostream & operator << (std::ostream & o, const COLOR & a) {
switch(a) {
case COLOR::RED: o << "RED";break;
case COLOR::GREEN: o << "GREEN";break;
default: o << static_cast<int>(a);
}
return o;
}
int main() {
COLOR c = COLOR::RED;
std::cout << c << std::endl;
return 0;
}
The drawback is, that you have to explicitly writeout all identifiers twice - once in class and once in operator.
One of the mayor advantages of enum class is that names are scoped and it does not allow stuff like:
std::string get(COLOR x);
...
get(3); //Compile time error
get(CAR::GOLF);//Compile time error
Judging by your comments on other answers, you can define your own identifier like this:
class MyId {
public:
int id;
static std::unordered_map<int,std::string> names;
};
std::ostream & operator << (std::ostream &o,const MyId & m) {
auto itr = MyId::names.find(m.id);
if(itr!= MyId::names.end()) {
o << itr->second;
} else {
o << "Unknown " << m.id;
}
return o;
}
Its typesafe, theres no more overhead then int, and it is able to survive user input.