C++位标志枚举转换为字符串

5
我将尝试模仿Visual Studio中Intellisense的功能,当您在调试时悬停在位枚举变量(或类似名称)上时,将枚举转换为字符串。例如:
#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;
}

如果你将鼠标悬停在yellow上,你会看到:

enter image description here

所以我想要能够调用类似于:
std::cout << BitwiseEnumToString(yellow) << std::endl;

我编写了以下内容,尝试提供一种通用的方法来打印枚举:

并将输出打印为:Green | Blue

#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();
}

现在我可以调用:

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

我得到了期望的输出。

我猜想我找不到相关信息,因为我不知道它叫什么,但是无论如何 -

  1. std或boost中是否有类似的功能或可用于提供此功能?

  2. 如果没有,实现这样的功能最有效的方法是什么?(或者我的方法足够了吗)


singleFlagToString()是什么?你是不是想调用ColorToString()呢?乍一看,其他的都没问题,但我会使用位移操作而不是index % 2 - πάντα ῥεῖ
singleFlagToString 是一个 std::function,它接受枚举并将其转换为 const char*。目的是尽可能地通用,因此如果您注意到,我使用第二个参数 ColorToString 调用了 BitwiseEnumToString - ZivS
你如何使用位移运算符来代替 index % 2?我可能可以使用 index & 0x1 == 0,但这不是位移操作,对吧? - ZivS
2个回答

0

编辑:请参见下面的通用template实现... 请注意,这个template实现会对几乎所有的ostreamoperator <<()实现进行破坏!如果enum是完整的类,并且有一个基类实现template,那么会更好。这个通用定义相当于在瓷器店里使用原子弹...


我写了下面这个例子,包含一个测试函数。它使用C++的重载功能,让你可以简单地cout一个Color——如果你想要仍然打印简单的数值,你需要将它转换为一个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()

通用实现,附带示例

首先,一个头文件:

// 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)

接下来,是你的例子:

// 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>()

接下来,一个关于值内部位的例子:

// 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>()

最后,一个测试程序:

#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()

很酷,是吧?


你没有打印“White”。 为什么这样更好呢? 每次更改枚举时,您必须更新字符串数组,并且不会在其中获得编译错误。 此外 - 您的解决方案并不是那么通用... - ZivS
我现在添加了一个零情况。 - John Burger
1
最终回答你的问题:不,没有通用的方法可以实现你想要的。虽然有其他编程语言可以将源代码常量转换为字符串,但C++不是其中之一。你需要自己将符号有效地复制为字符串。 - John Burger
这是一个不错的解决方案。我喜欢你使用了流操作符,也喜欢函数只需要一个参数进行类型推导(而不是像我一样发送特定的函数)。这可能是对我的方法的改进,而不是发送函数,我将在T上使用流操作符,从而强制T具有“ToString”运算符。我仍然不喜欢你必须创建一个字符串数组,因为我更喜欢在更改枚举时出现编译错误。再次提问-你为什么认为这是更好的解决方案? - ZivS
我并不是在暗示这是更好的解决方案。但它确实是通用的!你似乎想要一种“本地”的方法来做到这一点 - 这是不可能的。没有什么能阻止你不得不(重新)定义字符串数组。但是,通过我的 EnumBitStrings.h 文件,你可以(相对)轻松地为你的 enum 范围做你想要的事情。也许你可以加入一个 static_assert() 来确保字符串数组的大小至少与枚举数量相同... 以某种方式。 - John Burger

0
你需要维护一个枚举的字符串表示列表,可以使用向量、硬编码等方式实现。这是一种可能的实现方法。
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;
}

然后为您想要执行此操作的每个枚举创建另一个EnumToStr函数,遵循相同的形式


我不会给你打负分,但是这个解决方案根本不是我的问题的答案。它不是通用的,不可扩展的,性能也不好,只是展示了如何连接字符串... - ZivS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接