65

I declared a enum type as this,

enum WeekEnum
{
Mon = 0;
Tue = 1;
Wed = 2;
Thu = 3;
Fri = 4;
Sat = 5;
Sun = 6;
};

How can I get the item name "Mon, Tue, etc" when I already have the item value "0, 1, etc."

I already have a function as this

Log(Today is "2", enjoy! );

And now I want the output below

Today is Wed, enjoy

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Nano HE
  • 1,879
  • 7
  • 28
  • 39

11 Answers11

50

You can't directly, enum in C++ are not like Java enums.

The usual approach is to create a std::map<WeekEnum,std::string>.

std::map<WeekEnum,std::string> m;
m[Mon] = "Monday";
//...
m[Sun] = "Sunday";
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • 33
    I'd probably use an array before a map. – Mooing Duck Jul 30 '12 at 01:27
  • A std::unordered_map (a C++ 11 hashtable) would work too. @MooingDuck 's suggested array would be better for performance due to locality/caching reasons unless you had over ~ 100 enum types. – Darwyn Aug 18 '17 at 19:03
  • 5
    @Darwyn: An array is better unconditionally as long as you reserve the correct size. It's a trivial index operation to read or write, and there's never inserts or deletes. – Mooing Duck Aug 18 '17 at 20:30
  • @MooingDuck The lookup operation on an hashtable is O(1) vs the lookup operation on an array which is O(N). Which means eventually a hashtable will outperform (in terms of CPU time) as N gets larger. This blog entry sums it up with empirical data: http://scottmeyers.blogspot.de/2015/09/should-you-be-using-something-instead.html – Darwyn Aug 18 '17 at 21:26
  • 15
    @Darwyn: The lookup on a hashtable is O(1), but it's got a large constant, for the hash equation. The lookup operation on the array is literally direct indexing, there's no search or equation involved. `m[Mon]` is treated by the compiler as `m[1]`, which usually is a single opcode. You're thinking of looking up in array by value, rather than by index. – Mooing Duck Aug 18 '17 at 21:30
  • 2
    @MooingDuck You're right, I mistakenly thinking of lookup by value not by index. Thanks. – Darwyn Aug 18 '17 at 22:43
  • 8
    Using an array probably also implies that the enum values are contiguous. Even though most enums declared in practice probably are contiguous, there's nothing in the language that mandates it. – saxbophone Apr 20 '19 at 12:17
  • 1
    It also most of all mandates the enum values are not "really big" (which may be the case if they represent say bitfield flags) - all in all if you get enum *names* it probably means you're working with strings and that means some kind of io with memory allocations, copying and such - in that case even doing a logn lookup on a normal map to get a sting pointer will most probably not be something you'll find a need to optimize anytime soon (see https://godbolt.org/z/n58M466G8 for an example of such case) – RnR Apr 07 '22 at 06:53
28

Here is another neat trick to define enum using X Macro:

#include <iostream>

#define WEEK_DAYS \
X(MON, "Monday", true) \
X(TUE, "Tuesday", true) \
X(WED, "Wednesday", true) \
X(THU, "Thursday", true) \
X(FRI, "Friday", true) \
X(SAT, "Saturday", false) \
X(SUN, "Sunday", false)

#define X(day, name, workday) day,
enum WeekDay : size_t
{
    WEEK_DAYS
};
#undef X

#define X(day, name, workday) name,
char const *weekday_name[] =
{
    WEEK_DAYS
};
#undef X

#define X(day, name, workday) workday,
bool weekday_workday[]
{
    WEEK_DAYS
};
#undef X

int main()
{
    std::cout << "Enum value: " << WeekDay::THU << std::endl;
    std::cout << "Name string: " << weekday_name[WeekDay::THU] << std::endl;
    std::cout << std::boolalpha << "Work day: " << weekday_workday[WeekDay::THU] << std::endl;

    WeekDay wd = SUN;
    std::cout << "Enum value: " << wd << std::endl;
    std::cout << "Name string: " << weekday_name[wd] << std::endl;
    std::cout << std::boolalpha << "Work day: " << weekday_workday[wd] << std::endl;

    return 0;
}

Live Demo: https://ideone.com/bPAVTM

Outputs:

Enum value: 3
Name string: Thursday
Work day: true
Enum value: 6
Name string: Sunday
Work day: false
nik7
  • 806
  • 3
  • 12
  • 20
Killzone Kid
  • 6,171
  • 3
  • 17
  • 37
  • I ended up using this trick for an enum in a shared header. Since it was shared, though, I had to change the array into an inline function that returns the string if the value matches. (as in "if parameter == day return name;"). This approach is as ugly as it gets, but the maintainability is top notch. – Richard Dec 20 '20 at 12:13
  • Can you do this in a strongly typed way? Similar to `enum class`? – Weston McNamara Jan 11 '22 at 19:05
  • @WestonMcNamara yes, just as `enum class WeekDay { WEEK_DAYS ...`. – Matthieu Mar 04 '22 at 16:26
18

No, you have no way to get the "name" from the value in C++ because all the symbols are discarded during compilation.

You may need this way X Macros

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
RolandXu
  • 3,566
  • 2
  • 17
  • 23
  • 4
    This answer is too "total". There is no reason, why `__ENUM_NAME__(x)` does not exist, given there is stuff like `__FUNCTION__, __PRETTY_FUNCTION__` etc. already. The compiler guys could offer a similar solution for enums. Rust has something similar with `#[derive(Debug)]` or `#[derive(Display)]`. Haskell has `deriving Show` as a compiler goodie, which does the same. – BitTickler May 30 '22 at 13:31
9

You can define an operator that performs the output.

std::ostream& operator<<(std::ostream& lhs, WeekEnum e) {
    switch(e) {
    case Monday: lhs << "Monday"; break;
    .. etc
    }
    return lhs;
}
Puppy
  • 144,682
  • 38
  • 256
  • 465
  • gives me this error https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(LNK2005)&rd=true – Brandon Dec 18 '16 at 09:42
  • Brandon, if you got that error in MSVC that is the reason - I've had perfectly valid programs that compiled in expensive compilers like the Intel Compiler fail miserably in Visual Studio. It would error at something like an array with a #define constant as an index that would equal like 64 - claim that you can't have that as the index. It was an integer, not a float, boolean, or anything else. But it refused to work. I've had some of the same projects build in GCC, Mingw, Intel & older compilers like Turbo C++ & yet MSVC doesn't know what it's doing when it comes to enums or constants. – Seth D. Fulmer Jun 27 '20 at 12:12
8

An enumeration is something of an inverse-array. What I believe you want is this:

const char * Week[] = { "", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };  // The blank string at the beginning is so that Sunday is 1 instead of 0.
cout << "Today is " << Week[2] << ", enjoy!";  // Or whatever you'de like to do with it.
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
Cosine
  • 572
  • 4
  • 18
  • 9
    enum and array are two very different things. – Master Yoda Jun 01 '16 at 05:48
  • 2
    That is correct. While an enum matches (source) strings to numbers, an array could match numbers to strings. (See the relationship?) OP's title cannot be directly achieved, but her core problem can. Since her example has names in numerical order, she can use an array rather than a std::map (which is heavier). Therefore an array may be exactly what OP needs, despite not answering her title question. – Cosine Jun 02 '16 at 14:46
  • 1
    I downvoted this because it's useless if enum value and array index dont't match – Elvis Dukaj Apr 14 '20 at 12:52
3

On GCC it may look like this:

const char* WeekEnumNames [] = {
    [Mon] = "Mon",
    [Tue] = "Tue",
    [Wed] = "Wed",
    [Thu] = "Thu",
    [Fri] = "Fri",
    [Sat] = "Sat",
    [Sun] = "Sun",
};
rick-rick-rick
  • 203
  • 3
  • 5
1

How can I get the item name "Mon, Tue, etc" when I already have the item value "0, 1, etc."

On some older C code (quite some time ago), I found code analogous to:

std::string weekEnumToStr(int n)
{
   std::string s("unknown");
   switch (n)
   {
   case 0: { s = "Mon"; } break;
   case 1: { s = "Tue"; } break;
   case 2: { s = "Wed"; } break;
   case 3: { s = "Thu"; } break;
   case 4: { s = "Fri"; } break;
   case 5: { s = "Sat"; } break;
   case 6: { s = "Sun"; } break;
   }
   return s;
}

Con: This establishes a "pathological dependency" between the enumeration values and the function... meaning if you change the enum you must change the function to match. I suppose this is true even for a std::map.

I vaguely remember we found a utility to generate the function code from the enum code. The enum table length had grown to several hundred ... and at some point it is maybe a sound choice to write code to write code.


Note -

in an embedded system enhancement effort, my team replaced many tables (100+?) of null-terminated-strings used to map enum int values to their text strings.

The problem with the tables was that a value out of range was often not noticed because many of these tables were gathered into one region of code / memory, such that a value out-of-range reached past the named table end(s) and returned a null-terminated-string from some subsequent table.

Using the function-with-switch statement also allowed us to add an assert in the default clause of the switch. The asserts found several more coding errors during test, and our asserts were tied into a static-ram-system-log our field techs could search.

2785528
  • 5,438
  • 2
  • 18
  • 20
1

May not be the best solution ever, but this does the job pretty well.

The names of the enums are lazy loaded, so after the first call to to_string it will be loaded and kept in memory.

The method from_string was not implemented as it was not requested here, but it can be easily implemented by calling get_enum_names, searching the name in the vector, and casting its position to the enum type.

Please add the definition of get_enum_names in a cpp file (only the declaration should be in the header file).

It should work fine with C++ >= 11
Tested in gcc and MSVC

Implementation:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <unordered_map>

// Add the definition of this method into a cpp file. (only the declaration in the header)
const std::vector<std::string>& get_enum_names(const std::string& en_key, const std::string& en_str)
{
    static std::unordered_map<std::string, std::vector<std::string>> en_names_map;
    const auto it = en_names_map.find(en_key);
    if (it != en_names_map.end())
        return it->second;

    constexpr auto delim(',');
    std::vector<std::string> en_names;
    std::size_t start{};
    auto end = en_str.find(delim);
    while (end != std::string::npos)
    {
        while (en_str[start] == ' ')
            ++start;
        en_names.push_back(en_str.substr(start, end - start));
        start = end + 1;
        end = en_str.find(delim, start);
    }
    while (en_str[start] == ' ')
        ++start;
    en_names.push_back(en_str.substr(start));
    return en_names_map.emplace(en_key, std::move(en_names)).first->second;
}

#define DECLARE_ENUM(ENUM_NAME, ...)   \
    enum class ENUM_NAME{ __VA_ARGS__ }; \
    inline std::string to_string(ENUM_NAME en) \
    { \
        const auto& names = get_enum_names(#ENUM_NAME #__VA_ARGS__, #__VA_ARGS__); \
        return names[static_cast<std::size_t>(en)]; \
    }

Usage:

DECLARE_ENUM(WeekEnum, Mon, Tue, Wed, Thu, Fri, Sat, Sun);

int main()
{
    WeekEnum weekDay = WeekEnum::Wed;
    std::cout << to_string(weekDay) << std::endl; // prints Wed
    std::cout << to_string(WeekEnum::Sat) << std::endl; // prints Sat
    return 0;
}
rflobao
  • 562
  • 2
  • 8
  • 1
    @4LegsDrivenCat has kindly reported that it may not be necessary to provide the enum items __VA_ARGS__ appended in the key of the map. For most implementations it is true, but there's nothing that prevents enums with the same name to be declared into different scopes (like namespaces). Therefore the current solution ensures that enums with the same name can only share its items if both have the same items and the same order. Said that, the name of the enum ENUM_NAME is the redundant part of the key, but it's good to have for debug purposes. – rflobao May 11 '22 at 12:53
0

I have had excellent success with a technique which resembles the X macros pointed to by @RolandXu. We made heavy use of the stringize operator, too. The technique mitigates the maintenance nightmare when you have an application domain where items appear both as strings and as numerical tokens.

It comes in particularily handy when machine readable documentation is available so that the macro X(...) lines can be auto-generated. A new documentation would immediately result in a consistent program update covering the strings, enums and the dictionaries translating between them in both directions. (We were dealing with PCL6 tokens).

And while the preprocessor code looks pretty ugly, all those technicalities can be hidden in the header files which never have to be touched again, and neither do the source files. Everything is type safe. The only thing that changes is a text file containing all the X(...) lines, and that is possibly auto generated.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • 2
    Interesting prose regarding this macro-based approach, but how about some code? – Neil Justice Jan 26 '16 at 21:10
  • @NeilJustice Did you read about [X macros](http://www.drdobbs.com/the-new-c-x-macros/184401387)? That's about what we did, in C++ with a map and a vector, allowing to go back and forth between enums and string representations and token values. – Peter - Reinstate Monica Jan 26 '16 at 23:17
0

If you know the actual enum labels correlated to their values, you can use containers and C++17's std::string_view to quickly access values and their string representations with the [ ] operator while tracking it yourself. std::string_view will only allocate memory when created. They can also be designated with static constexpr if you want them available at run-time for more performance savings. This little console app should be fairly fast.

#include <iostream>
#include <string_view>
#include <tuple>    
int main() {
    enum class Weekdays { //default behavior starts at 0 and iterates by 1 per entry
        Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
    };

    static constexpr std::string_view Monday    = "Monday";
    static constexpr std::string_view Tuesday   = "Tuesday";
    static constexpr std::string_view Wednesday = "Wednesday";
    static constexpr std::string_view Thursday  = "Thursday";
    static constexpr std::string_view Friday    = "Friday";
    static constexpr std::string_view Saturday  = "Saturday";
    static constexpr std::string_view Sunday    = "Sunday";
    static constexpr std::string_view opener    = "enum[";
    static constexpr std::string_view closer    = "] is ";
    static constexpr std::string_view semi      = ":";

    std::pair<Weekdays, std::string_view> Weekdays_List[] = {
        std::make_pair(Weekdays::Monday,    Monday),
        std::make_pair(Weekdays::Tuesday,   Tuesday),
        std::make_pair(Weekdays::Wednesday, Wednesday),
        std::make_pair(Weekdays::Thursday,  Thursday),
        std::make_pair(Weekdays::Friday,    Friday),
        std::make_pair(Weekdays::Saturday,  Saturday),
        std::make_pair(Weekdays::Sunday,    Sunday)
    };

    for (int i=0;i<sizeof(Weekdays_List)/sizeof(Weekdays_List[0]);i++) {
        std::cout<<opener<<i<<closer<<Weekdays_List[(int)i].second<<semi\
        <<(int)Weekdays_List[(int)i].first<<std::endl;
    }    
    return 0;
}

Output:

enum[0] is Monday:0
enum[1] is Tuesday:1
enum[2] is Wednesday:2
enum[3] is Thursday:3
enum[4] is Friday:4
enum[5] is Saturday:5
enum[6] is Sunday:6
kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
0

The solution I prefer is to mix arrays and ostream like this:

std::ostream& operator<<(std::ostream& lhs, WeekEnum e) {
    static const std::array<std::string, 7> WEEK_STRINGS = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" };

    return lhs << WEEK_STRINGS[static_cast<WeekEnum>(e)]
}

cout << "Today is " << WeekEnum::Monday;

I also suggest to use enum class instead of enum.

snowball
  • 15
  • 1
  • 5
Elvis Dukaj
  • 7,142
  • 12
  • 43
  • 85