9

How do I 'ToString()' an enum in C++?

In Java and C# I would just call ToString.

enum Colours
{
    Red =0,
    Green=1,
    Blue=2
};

I need to create a string like: "Invalid colour '" + colour + "' selected."

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
CodingHero
  • 2,865
  • 6
  • 29
  • 42

8 Answers8

23

While this is commonly done through switches, I prefer arrays:

#include <iostream>

namespace foo {
  enum Colors { BLUE = 0, RED, GREEN, SIZE_OF_ENUM };
  static const char* ColorNames[] = { "blue", "red", "green" };

  // statically check that the size of ColorNames fits the number of Colors
  static_assert(sizeof(foo::ColorNames)/sizeof(char*) == foo::SIZE_OF_ENUM
    , "sizes dont match");
} // foo

int main()
{
  std::cout << foo::ColorNames[foo::BLUE] << std::endl;
  return 0;
}

The explicit array size has the benefit of generating a compile time error should the size of the enum change and you forget to add the appropriate string.

Alternatively, there is Boost.Enum in the Boost vault. The library hasn't been officially released but is quite stable and provides what you want. I wouldn't recommend it to a novice though.

pmr
  • 58,701
  • 10
  • 113
  • 156
  • 3
    It does not generate an error if you forget to *add* strings... – Matthieu M. Feb 05 '12 at 18:29
  • 1
    @MatthieuM. True. The only reliable way to build something like this are probably macros. – pmr Feb 05 '12 at 18:37
  • I am afraid so. I've always thought it was a shame as well, I would have appreciated the compiler building the function on the side. One way I found was using macros, though this is inlines the function more or less. The other was to use tests to make sure the translation worked for previous types (at least) and then rely on the developer to *think* :) – Matthieu M. Feb 05 '12 at 19:55
  • @MatthieuM. I guess the real solution would still be to use a `const unordered_map`. That shields against nearly everything a maintainer can possibly do to the enum and resistant to misuse at runtime but is somewhat expensive. – pmr Feb 05 '12 at 20:00
  • replace `ColorNames[SIZE_OF_ENUM]` with `ColorNames[]` and add `static_assert(sizeof(foo::ColorNames)/sizeof(char*) == foo::SIZE_OF_ENUM, "sizes dont match");` – tcb Jul 21 '15 at 23:53
  • @tcb Thank you. Execellent suggestion. – pmr Jul 22 '15 at 07:06
18

How about a little magic with macros:

#include <iostream>
#include <string>
#include <vector>


// http://stackoverflow.com/questions/236129/how-to-split-a-string-in-c
std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    int start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        tokens.push_back(text.substr(start, end - start));
        start = end + 1;
    }
    tokens.push_back(text.substr(start));
    return tokens;
}

#define ENUM(name, ...)\
enum name \
{\
__VA_ARGS__\
};\
std::vector<std::string> name##Map = split(#__VA_ARGS__, ',');\
    std::string toString(const name v) { return name##Map.at(v);}


ENUM(Color, Red,Green,Blue)


int main(int c, char**v)
{
    std::cout << toString(Red) << toString(Blue);
    return 0;//a.exec();
}

Yes, I understand that this is ugly and you'd better not do do such things

Lol4t0
  • 12,444
  • 4
  • 29
  • 65
8

That's inherently impossible.

A C++ enum is just a set of numbers with compile-time names.
At runtime, they are indistinguishable from ordinary numbers.

You need to write a switch statement that returns a string.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
5

I really like the macro approach of @Lol4t0.

I extended it to be able to convert an enum from a string too:

#include <iostream>
#include <string>
#include <vector>

// http://stackoverflow.com/questions/236129/how-to-split-a-string-in-c
std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    int start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        tokens.push_back(text.substr(start, end - start));
        start = end + 1;
    }
    tokens.push_back(text.substr(start));
    return tokens;
}

#define ENUM(name, ...)\
    enum name\
    {\
        __VA_ARGS__\
    };\
    static const int name##Size = (sizeof((int[]){__VA_ARGS__})/sizeof(int));\
    static const vector<string> name##ToStringMap = split(#__VA_ARGS__, ',');\
    const string name##ToString(const name value)\
    {\
        return name##ToStringMap.at(value);\
    };\
    map<string, name> name##ToFromStringMap(...)\
    {\
        map<string, name> m;\
        name args[name##Size] = { __VA_ARGS__ };\
        \
        int i;\
        for(i = 0; i < name##Size; ++i)\
        {\
            m[name##ToString(args[i])] = args[i];\
        }\
        return m;\
    };\
    static map<string, name> name##FromStringMap = name##ToFromStringMap(__VA_ARGS__);\
    const name name##FromString(const string value, const name defaultValue)\
    {\
        if(name##FromStringMap.count(value) == 0)\
        {\
            return defaultValue;\
        }\
        return name##FromStringMap[value];\
    };

Usage:

ENUM(MyEnum, Value1, Value2)

void main()
{
    string valueName = MyEnumToString(MyEnum::Value2);
    MyEnum value = MyEnumFromString(valueName, MyEnum::Value1);
}

I am not a C++ expert, so let me know what you think or how to do better.

me.
  • 177
  • 2
  • 8
3
enum Color
{
    Red =0,
    Green=1,
    Blue=2
};

std::string ColorMap[] = { "Red", "Green","Blue" };

Use ColorMap[c] to get the string representation:

std::string msg = "Invalid colour '" + ColorMap[c] + "' selected.";

However, if the values of enum are not continuous, then you can use std::map instead as:

enum Color
{
    Red   = 0x1,
    Green = 0x2,
    Blue  = 0x4, 
    Black = 0x8, 
};

//C++11 only, as it uses std::initializer_list
std::map<Color, std::string> ColorMap = {
    {Red, "Red"},
    {Green, "Green"},
    {Blue, "Blue"},
    {Black, "Black"}
};

//same as before!
std::string msg = "Invalid colour '" + ColorMap[c] + "' selected.";
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    In my opinion the best solution since this approach can also be used with enums which contains gaps. E.g. Red=0x01, Green, Blue=0x10, Yellow. – Anonymous Jun 19 '14 at 08:17
1

You have to do it manually, i.e.

const char* ToString(Colours co) {
     switch(co) {
        case Red:
           return "Red";
        // ...
     }
}

A lookup table would also be possible. I've also seen people using custom scripts to generate such stuff on top of their source code.

Alexander Gessler
  • 45,603
  • 7
  • 82
  • 122
0

You can store the names in an array of strings, indexed by the enum values.

enum Colours
{
    Red =0,
    Green=1,
    Blue=2
};

char* names[3] = {"Red", "Green", "Blue"};

Then you can print: "Invalid colour '" + names[colour] + "' selected."

But this approach may not be very useful if you do not define the enum values sequentially. In that case this approach will waste memory. Writing a function with a switch over the enum value would be useful, as Alexander Gessler has mentioned. Another alternative may be the map from STL.

Sufian Latif
  • 13,086
  • 3
  • 33
  • 70
0

As @FlopCoder said:

enum Colours
{
    Red =0,
    Green=1,
    Blue=2
};
char* ColourNames[] = { "Red", "Green", "Blue" };
int colour = Green;
printf( "Invalid colour '%s' selected.", ColourNames[ colour ] );

This of course will only work if your enum starts at 0 and is continuous.
@Nawaz's way is more C++ stylish though.

Petruza
  • 11,744
  • 25
  • 84
  • 136