5

I'm trying to do what Intellisense does in visual studio when you hover over a bitwise-enum (or however it's called) variable (while debugging), by taking an enum and converting it to string.

for example:

#include <iostream>

enum Color {
    White = 0x0000,
    Red = 0x0001,
    Green = 0x0002,
    Blue = 0x0004,
};

int main()
{
    Color yellow = Color(Green | Blue);
    std::cout << yellow << std::endl;
    return 0;
}

If you hover over yellow you'll see:

enter image description here

So I want to be able to call something like:

std::cout << BitwiseEnumToString(yellow) << std::endl;

and have the output print: Green | Blue.

I wrote the following which tries to provide a generic way of for printing an enum:

#include <string>
#include <functional>
#include <sstream>

const char* ColorToString(Color color)
{
    switch (color)
    {
    case White:
        return "White";
    case Red:
        return "Red";
    case Green:
        return "Green";
    case Blue:
        return "Blue";
    default:
        return "Unknown Color";
    }
}

template <typename T>
std::string BitwiseEnumToString(T flags, const std::function<const char*(T)>& singleFlagToString)
{
    if (flags == 0)
    {
        return singleFlagToString(flags);
    }

    int index = flags;
    int mask = 1;
    bool isFirst = true;
    std::ostringstream oss;
    while (index)
    {
        if (index % 2 != 0)
        {
            if (!isFirst)
            {
                oss << " | ";
            }
            oss << singleFlagToString((T)(flags & mask));
            isFirst = false;
        }

        index = index >> 1;
        mask = mask << 1;
    }
    return oss.str();
}

So now I can call:

int main()
{
    Color yellow = Color(Green | Blue);
    std::cout << BitwiseEnumToString<Color>(yellow, ColorToString) << std::endl;
    return 0;
}

I get the desired output.

I'm guessing that I couldn't find anything about it since I don't know how it's called, but anyways -

  1. Is there something in std or boost that does that or can be used to provide this?

  2. If not, what's the most efficient way to do such a thing? (or would mine suffic)

ZivS
  • 2,094
  • 2
  • 27
  • 48
  • What is `singleFlagToString()`? Did you mean to call `ColorToString()` instead? At a 1st glance everything else looks fine to me, I would use a bitshift operation rather than the `index % 2` stuff though. – πάντα ῥεῖ Jun 13 '16 at 16:07
  • singleFlagToString is a `std::function` that takes the enum and converts it to `const char*`. the purpose was to be as generic as I can, so if you notice, I call `BitwiseEnumToString` with a second parameter `ColorToString`. – ZivS Jun 13 '16 at 16:23
  • And how would you use a bitshift operator instead of `index % 2`? I can probably use `index & 0x1 == 0` but it's not bit shifting right? – ZivS Jun 13 '16 at 16:25

2 Answers2

0

EDIT: See below for a generic, template implementation... Note, though, that this template implementation tramples ALL OVER ostream's operator <<() implemantations for practically everything! It'd be better if the enums were full-blown classes, with a base class implementation for the template. This generic definition is the equivalent of an atom bomb in a china shop...


I wrote the following example, with a test function. It uses C++ overloads to allow you to simply cout a Color - if you want to be able to still print the simple numerical value, you would have to cast it into an int:

#include <iostream>

enum Color {
    White = 0x0000,
    Red   = 0x0001,
    Green = 0x0002,
    Blue  = 0x0004,
}; // Color

std::ostream &operator <<(std::ostream &os, Color color) {
    static const char *colors[] = { "Red", "Green", "Blue", 0 }; // Synchronise with Color enum!

    // For each possible color string...
    for (const char * const *ptr = colors;
         *ptr != 0;
         ++ptr) {

        // Get whether to print something
        bool output = (color & 0x01)!=0;

        // Is color bit set?
        if (output) {
            // Yes! Output that string.
            os << *ptr;
        } // if

        // Next bit in color
        color = (Color)(color >> 1);

        // All done?
        if (color == 0) {
            // Yes! Leave
            break;
        } // if

        // No, so show some more...
        if (output) {
           // If output something, need 'OR'
           os << " | ";
        } // if
    } // for
    return os;
} // operator <<(Color)

void PrintColor() {
    for (unsigned c = 0; c < 8; ++c) {
        Color color = Color(c);
        std::cout << color << std::endl;
    } // fors
} // PrintColor()

Generic implementation, with examples

First, a header file:

// EnumBitString.h

template <typename ENUM>
const char * const *Strings() {
    static const char *strings[] = { "Zero", 0 }; // By default there are no Strings
    return strings;
} // Strings<ENUM>()

template <typename ENUM>
std::ostream &operator <<(std::ostream &os, ENUM e) {
    const char * const *ptr = Strings<ENUM>();
    if (e == 0) {
        os.operator <<(*ptr);
        return os;
    } // if

    // For each possible ENUM string...
    while (*ptr != 0) {
        bool output = (e & 0x01) != 0;

        // Is bit set?
        if (output) {
            // Yes! Output that string.
            os.operator <<(*ptr);
        } // if

        // Next bit in e
        e = (ENUM)(e >> 1);

        // All done?
        if (e == 0) {
            // Yes! Leave
            break;
        } // if

        // No, so show some more...
        if (output) {
            os.operator <<(" | ");
        } // if

        ++ptr;
    } // while
    return os;
} // operator <<(ENUM)

Next, your example:

// Colors.h

#include "EnumBitString.h"

enum Colors {
    White = 0x0000,
    Red   = 0x0001,
    Green = 0x0002,
    Blue  = 0x0004,
    NumColors = 4
}; // Colors

template <>
const char * const *Strings<Colors>() {
    static const char *strings[] { "White", // Zero case
                                   "Red",
                                   "Green",
                                   "Blue",
                                   0 }; // Don't forget final 0
    static_assert((sizeof(strings)/sizeof(strings[0])==NumColors+1, "Colors mismatch!");
    return strings;
} // Strings<Colors>()

Then, another example of bits within a value:

// Flags.h

#include "EnumBitString.h"

enum Flags {
    CF = 0x0001,
//  Res1 = 0x02,
    PF = 0x0004,
//  Res2 = 0x08,
    AF = 0x0010,
//  Res3 = 0x20,
    ZF = 0x0040,
    NumFlags = 7
}; // Flags

template <>
const char * const *Strings<Flags>() {
    static const char *strings[] =  { "None",
                                      "Carry",
                                      "",
                                      "Parity",
                                      "",
                                      "Arithmetic",
                                      "",
                                      "Zero",
                                      0 }; // Don't forget final 0
    static_assert((sizeof(strings)/sizeof(strings[0])==NumFlags+1, "Flags mismatch!");
    return strings;
} // Strings<Flags>()

Finally, a test program:

#include <iostream>

#include "Colors.h"
#include "Flags.h"

void TestENUM() {
    for (unsigned c = 0; c < 0x0008; ++c) {
        Colors color = Colors(c);
        std::cout << color << std::endl;
    } // for
    for (unsigned f = 0; f < 0x0080; ++f) {
        Flags flag = Flags(f);
        std::cout << flag << std::endl;
    } // for
} // TestENUM()

Cool, huh?

Community
  • 1
  • 1
John Burger
  • 3,662
  • 1
  • 13
  • 23
  • You're not printing "White". Why is this better? You have to update the array of strings every time you change the enum and you won't get a compilation error on it. Also - your solution is not that generic... – ZivS Jun 13 '16 at 17:20
  • I've now added a Zero case. – John Burger Jun 13 '16 at 17:31
  • 1
    Ultimately, in answer to your original question: no, there is no generic method for what you want.. There are other programming languages that "stringify" source-code constants, but C++ is not one of them. You need to (effectively) duplicate your symbols as strings yourself. – John Burger Jun 13 '16 at 17:35
  • It's a nice solution. I liked the fact that you used the stream operator and also the fact that the function needs only 1 parameter for type deduction (instead of sending a specific function like I did). This could be an improvement to my method, instead of sending the function I will just use the stream operator on `T` thus forcing `T` to have a "ToString" operator. I still don't like the fact that you have to create an array of strings becuase I prefer compile errors when changing enums. And again - why do you think this is a better solution? – ZivS Jun 13 '16 at 17:44
  • I'm not suggesting it is a better solution. It _is_ generic though! You seemed to want a "native" way to do it - which wasn't going to happen. Nothing will prevent you from having to (re)define the array of strings. But, with my `EnumBitStrings.h` file, you can (relatively) easily do what you want for a range of your `enum`s. Perhaps you can put in a `static_assert()` to ensure that the size of the string array is at least the same as the number of enums... somehow. – John Burger Jun 13 '16 at 17:47
0

You're going to have to maintain a list of the string representations of your enum, be it in a vector, hard-coded, etc. This is one possible implementation.

enum Color : char
{
  White = 0x00,
  Red   = 0x01,
  Green = 0x02,
  Blue  = 0x04,
  //any others
}

std::string EnumToStr(Color color)
{
  std::string response;

  if(color & Color::White)
    response += "White | ";
  if(color & Color::Red)
    response += "Red | ";
  if(color & Color::Green)
    response += "Green | ";
  if(color & Color::Blue)
    response += "Blue | ";
  //do this for as many colors as you wish

  if(response.empty())
    response = "Unknown Color";
  else
    response.erase(response.end() - 3, response.end());

  return response;
}

Then make another EnumToStr function for each enum you want to do this to, following the same form

Altainia
  • 1,347
  • 1
  • 7
  • 11
  • I won't -1 you but this solution is not at all an answer to my question. It is not generic, not scalable, not better performance wise and is simply showing how to concatenate strings... – ZivS Jun 13 '16 at 17:50