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.