8

Consider, I have got the following enum class:

enum class TestEnum
{
   None = 0,
   Foo,
   Bar
};

I'd like to specify ostream operator ( << ) for this enum class, so I could write:

std::cout << "This is " << TestEnum::Foo;

and get following output This is Foo.

My question is:
Is there any place where enum "name specifiers" are stored? (i.e. for enum class TestEnum it is None, Foo and Bar) So I could write a function (or at best function template) that specifies ostream operator for this TestEnum like:

std::ostream& operator<< ( std::ostream& os, TestEnum aEnum ) {
   return std::string( aEnum.name() );
}

So far, I did it this way:

std::ostream& operator<< ( std::ostream& os, TestEnum aEnum ) {
   switch( aEnum )
   {
   case TestEnum::Foo:
       os << "Foo";
       break;
   case TestEnum::Bar:
       os << "Bar"
       break;
   }
   return os;
}

I have seen some solutions using boost library, but I would prefer not using it this time.

JeJo
  • 30,635
  • 6
  • 49
  • 88
TenerowiczS
  • 147
  • 6
  • 4
    As of today those names aren't stored anywhere. C++ is not a reflective language (yet). – StoryTeller - Unslander Monica Jun 03 '22 at 11:10
  • 2
    The way you did it is sensible. For a very large enum set of enumerators, some people may use a `std::map` or some other strategy to make dealing with all the names a bit more manageable. – Eljay Jun 03 '22 at 11:15

2 Answers2

7

Is there any place where enum "name specifiers" are stored?

No, but one option is to use std::map<TestEnum, std::string> as shown below:

enum class TestEnum
{
   None = 0,
   Foo,
   Bar
};
const std::map<TestEnum,std::string> myMap{{TestEnum::None, "None"}, 
                                     {TestEnum::Foo, "Foo"}, 
                                     {TestEnum::Bar, "Bar"}};
    
std::ostream& operator<< ( std::ostream& os, TestEnum aEnum )
{
   os << myMap.at(aEnum);
   return os;
}
int main()
{
    std::cout << "This is " << TestEnum::Foo; //prints This is Foo 
    std::cout << "This is " << TestEnum::Bar; //prints This is Bar

    return 0;
}

Demo

Jason
  • 36,170
  • 5
  • 26
  • 60
  • 3
    I think `string_view` may be more relevant here. Also that `map` should probably be `const` to avoid unwanted modifications by `operator[]`. – Erel Jun 03 '22 at 11:50
  • @Erel Note that i didn't use `operator[]` but instead used `std::map::at` so no unwanted modification may be done here. Moreover, i was just showing one basic usage to make my point, feel free to modify it in your code according to how you see fit. – Jason Jun 03 '22 at 11:53
  • @AnoopRana `std::map::at()` only performs bounds checking, you can modify the map through it the same way you can do it with `std::map::operator[]()`. If the map is non-const, then the returned reference can be used to modify the element, whether you used the subscript operator or the `at()` member function. – Fareanor Jun 03 '22 at 12:14
  • @Fareanor In the first comment by Erel, they said map should probably be const to avoid unwanted modifications **by `operator[]`**. My reply was to that particular comment only which mentions that i did not use `operator[]` in my example. So instead of saying `operator[]` they should've said `at`. Ofcourse, i know that as the map is not const it could be modified by other means as you mentioned. But nowhere in my example i tried to do so. Anyway i have added `const` now. – Jason Jun 03 '22 at 12:26
  • @AnoopRana Oh right I was confused because you said _"[...] **so** no unwanted modification[...]"_ as if it was a consequence of using `at()`. We are therefore in agreement :) – Fareanor Jun 03 '22 at 13:46
6

Is there any place where enum "name specifiers" are stored?

No, the names are not saved anywhere. If required, one needs to (unfortunately) make a mapping of them.

If the enum values can be used for array indexing (like OP's case) one might can make a mapping using array of std::string_view(required or later, otherwise array of std::strings).

The usage of array, makes the following solution lightweight and O(1) look-up.

#include <iostream>
#include <string_view>
#include <type_traits> // std::underlying_type_t
using namespace std::string_view_literals;

enum struct TestEnum { None = 0,   Foo,   Bar };
inline static constexpr std::string_view enumArray[]{"None"sv, "Foo"sv, "Bar"sv};

std::ostream& operator<< (std::ostream& os, TestEnum aEnum)
{
    return os << enumArray[static_cast<std::underlying_type_t<TestEnum>>(aEnum)];
}

int main()
{
    std::cout << "This is " << TestEnum::Foo; // prints: "This is Foo" 
    std::cout << "This is " << TestEnum::Bar; // prints: "This is Bar"
}

See a demo

JeJo
  • 30,635
  • 6
  • 49
  • 88
  • But this will require to have identity mapping between your `enumArray` and the `enum` values, which may be a bit troublesome to achieve if you have values like `enum class some_enum{foo = 0x1600, bar = 0x1400, ...};` – Erel Jun 03 '22 at 11:49
  • Well of course, but I think it's still fair to mention limitations of solutions for people reading this question and its answers. Nothing guarantees everyone will be in the exact same configuration as the OP. What I've written above may or may not be obvious for beginners, hence my comment. – Erel Jun 03 '22 at 11:56