C++ 中的哪个类型安全枚举 你正在用吗?

发布于 2024-07-08 06:19:55 字数 1610 浏览 11 评论 0原文

众所周知,C++ 中的内置枚举不是类型安全的。 我想知道那里使用了哪些实现类型安全枚举的类...... 我自己使用以下“自行车”,但它有点冗长和有限:

typesafeenum.h:

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

typesafeenum.cpp:

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

用法:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

添加: 我认为我应该更具体地说明要求。 我将尝试总结它们:

优先级 1:将枚举变量设置为无效值应该是不可能的(编译时错误),无一例外。

优先级 2:通过单个显式函数/方法调用应该可以将枚举值转换为 int 或从 int 转换。

优先级 3:尽可能紧凑、优雅和方便的声明和使用

优先级 4:将枚举值与字符串相互转换。

优先级 5:(很高兴拥有)迭代枚举值的可能性。

It is common knowledge that built-in enums in C++ are not typesafe.
I was wondering which classes implementing typesafe enums are used out there...
I myself use the following "bicycle", but it is somewhat verbose and limited:

typesafeenum.h:

struct TypesafeEnum
{
// Construction:
public:
    TypesafeEnum(): id (next_id++), name("") {}
    TypesafeEnum(const std::string& n): id(next_id++), name(n) {}

// Operations:
public:
    bool operator == (const TypesafeEnum& right) const;
    bool operator != (const TypesafeEnum& right) const;
    bool operator < (const TypesafeEnum& right) const;

    std::string to_string() const { return name; }

// Implementation:
private:
    static int next_id;
    int id;
    std::string name;
};

typesafeenum.cpp:

int TypesafeEnum::next_id = 1;

bool TypesafeEnum::operator== (const TypesafeEnum& right) const 
{ return id == right.id; }

bool TypesafeEnum::operator!= (const TypesafeEnum& right) const 
{ return !operator== (right); }

bool TypesafeEnum::operator< (const TypesafeEnum& right) const  
{ return id < right.id; }

Usage:

class Dialog 
{
 ...
    struct Result: public TypesafeEnum
    {
        static const Result CANCEL("Cancel");
        static const Result OK("Ok");
    };


    Result doModal();
 ...
};

const Dialog::Result Dialog::Result::OK;
const Dialog::Result Dialog::Result::CANCEL;

Addition:
I think I should have been more specific about the requirements. I'll try to summarize them:

Priority 1: Setting an enum variable to an invalid value should be impossible (a compile-time error) with no exceptions.

Priority 2: Converting an enum value to/from an int should be possible with a single explicit function/method call.

Priority 3: As compact, elegant and convenient declaration and usage as possible

Priority 4: Converting enum values to and from strings.

Priority 5: (Nice to have) Possibility to iterate over enum values.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

最偏执的依靠 2024-07-15 06:19:55

我目前正在研究来自 Boost Vault 的 Boost.Enum 提案(文件名 enum_rev4.6.zip)。 尽管它从未正式提交纳入 Boost,但它可以按原样使用。 (缺乏文档,但可以通过清晰的源代码和良好的测试来弥补。)

Boost.Enum 允许您像这样声明一个枚举:

BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

并让它自动扩展为:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};

它满足您列出的所有五个优先级。

I'm currently playing around with the Boost.Enum proposal from the Boost Vault (filename enum_rev4.6.zip). Although it was never officially submitted for inclusion into Boost, it's useable as-is. (Documentation is lacking but is made up for by clear source code and good tests.)

Boost.Enum lets you declare an enum like this:

BOOST_ENUM_VALUES(Level, const char*,
    (Abort)("unrecoverable problem")
    (Error)("recoverable problem")
    (Alert)("unexpected behavior")
    (Info) ("expected behavior")
    (Trace)("normal flow of execution")
    (Debug)("detailed object state listings")
)

And have it automatically expand to this:

class Level : public boost::detail::enum_base<Level, string>
{
public:
    enum domain
    {
        Abort,
        Error,
        Alert,
        Info,
        Trace,
        Debug,
    };

    BOOST_STATIC_CONSTANT(index_type, size = 6);

    Level() {}
    Level(domain index) : boost::detail::enum_base<Level, string>(index) {}

    typedef boost::optional<Level> optional;
    static optional get_by_name(const char* str)
    {
        if(strcmp(str, "Abort") == 0) return optional(Abort);
        if(strcmp(str, "Error") == 0) return optional(Error);
        if(strcmp(str, "Alert") == 0) return optional(Alert);
        if(strcmp(str, "Info") == 0) return optional(Info);
        if(strcmp(str, "Trace") == 0) return optional(Trace);
        if(strcmp(str, "Debug") == 0) return optional(Debug);
        return optional();
    }

private:
    friend class boost::detail::enum_base<Level, string>;
    static const char* names(domain index)
    {
        switch(index)
        {
        case Abort: return "Abort";
        case Error: return "Error";
        case Alert: return "Alert";
        case Info: return "Info";
        case Trace: return "Trace";
        case Debug: return "Debug";
        default: return NULL;
        }
    }

    typedef boost::optional<value_type> optional_value;
    static optional_value values(domain index)
    {
        switch(index)
        {
        case Abort: return optional_value("unrecoverable problem");
        case Error: return optional_value("recoverable problem");
        case Alert: return optional_value("unexpected behavior");
        case Info: return optional_value("expected behavior");
        case Trace: return optional_value("normal flow of execution");
        case Debug: return optional_value("detailed object state listings");
        default: return optional_value();
        }
    }
};

It satisfies all five of the priorities which you list.

痴情换悲伤 2024-07-15 06:19:55

一个不错的折衷方法是:

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

它与您的版本不同,不是类型安全的,但用法比标准枚举更好,并且您仍然可以在需要时利用整数转换。

A nice compromise method is this:

struct Flintstones {
   enum E {
      Fred,
      Barney,
      Wilma
   };
};

Flintstones::E fred = Flintstones::Fred;
Flintstones::E barney = Flintstones::Barney;

It's not typesafe in the same sense that your version is, but the usage is nicer than standard enums, and you can still take advantage of integer conversion when you need it.

不知在何时 2024-07-15 06:19:55

我使用 C++0x 类型安全枚举。 我使用一些提供往返字符串功能的辅助模板/宏。

enum class Result { Ok, Cancel};

I use C++0x typesafe enums. I use some helper template/macros that provide the to/from string functionality.

enum class Result { Ok, Cancel};
删除会话 2024-07-15 06:19:55

我不。 开销太大却收效甚微。 此外,能够将枚举转换为不同的数据类型以进行序列化是一个非常方便的工具。 我从未见过一个实例,其中“类型安全”枚举值得开销和复杂性,而 C++ 已经提供了足够好的实现。

I don't. Way too much overhead for little benefit. Also, being able to caste enumerations to different data types for serialization is a very handy tool. I have never seen an instance where a "Type safe" enumeration would be worth the overhead and complexity where C++ offers a good enough implementation already.

骄兵必败 2024-07-15 06:19:55

我的看法是,你正在发明一个问题,然后找到一个解决方案。 我认为没有必要为价值观的枚举制定一个复杂的框架。 如果您致力于让您的值仅成为某个集合的成员,那么您可以破解唯一集合数据类型的变体。

My take is that you're inventing a problem and then fitting a solution onto it. I see no need to do an elaborate framework for an enumeration of values. If you are dedicated to having your values only be members of a certain set, you could hack up a variant of a unique set datatype.

纵性 2024-07-15 06:19:55

我个人使用的是typesafe enum idiom的改编版本。 它没有提供您在编辑中所述的所有五个“要求”,但无论如何我强烈不同意其中的一些要求。 例如,我不明白 Prio#4(将值转换为字符串)与类型安全有什么关系。 大多数时候,单个值的字符串表示应该与类型的定义分开(考虑 i18n 的原因很简单)。 Prio#5(迭代,可选)是我希望看到自然在枚举中发生的最好的事情之一,所以我对它在您的请求中显示为“可选”感到难过,但是看来最好通过 单独的迭代系统来解决作为 begin/end 函数或 enum_iterator,这使得它们能够与 STL 和 C++11 foreach 无缝协作。

OTOH 这个简单的习惯用法很好地提供了 Prio#3 Prio#1,因为它主要只用更多类型信息包装 enum 。 更不用说这是一个非常简单的解决方案,在大多数情况下不需要任何外部依赖标头,因此它很容易携带。 它还具有使枚举范围为 a-la-C++11 的优点:

// This doesn't compile, and if it did it wouldn't work anyway
enum colors { salmon, .... };
enum fishes { salmon, .... };

// This, however, works seamlessly.
struct colors_def { enum type { salmon, .... }; };
struct fishes_def { enum type { salmon, .... }; };

typedef typesafe_enum<colors_def> colors;
typedef typesafe_enum<fishes_def> fishes;

该解决方案提供的唯一“漏洞”是它没有解决它不阻止枚举的事实不同类型(或 enum 和 int)的直接比较,因为当您直接使用值时,您会强制隐式转换为 int

if (colors::salmon == fishes::salmon) { .../* Ooops! */... }

但到目前为止,我已经发现此类问题可以通过简单地为编译器提供更好的比较来解决 - 例如,显式提供一个比较任意两个不同 enum 类型的运算符,然后强制其失败

// I'm using backports of C++11 utilities like static_assert and enable_if
template <typename Enum1, typename Enum2>
typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool >
::type operator== (Enum1, Enum2) {
    static_assert (false, "Comparing enumerations of different types!");
}

:到目前为止,要破坏代码,并且它确实明确地处理了特定问题而不做其他事情,我不确定这样的事情是“应该”做的事情(我怀疑它会干扰enum 已经参与了在其他地方声明的转换运算符;我很乐意收到有关此的评论)。

将此与上述类型安全习惯用法相结合,在人性化(可读性和可维护性)方面与 C++11 枚举类相对接近,而无需做任何过于晦涩的事情。 我必须承认这很有趣,我从来没有想过要真正询问编译器我是否正在处理enum...

I'm personally using an adapted version of the typesafe enum idiom. It doesn't provide all the five "requirements" that you've stated in your edit, but I strongly disagree with some of them anyway. For example, I don't see how Prio#4 (conversion of values to strings) has anything to do with type safety. Most of the time string representation of individual values should be separate from the definition of the type anyway (think i18n for a simple reason why). Prio#5 (iteratio, which is optional) is one of the nicest things I'd like to see naturally happening in enums, so I felt sad that it appears as "optional" in your request, but it seems it is better addressed via a separate iteration system such as begin/end functions or an enum_iterator, which makes them work seamlessly with STL and C++11 foreach.

OTOH this simple idiom nicely provides Prio#3 Prio#1 thanks to the fact that it mostly only wraps enums with more type information. Not to mention it is a very simple solution that for the most part doesn't require any external dependency headers, so it's pretty easy to carry around. It also has the advantage of making enumerations scoped a-la-C++11:

// This doesn't compile, and if it did it wouldn't work anyway
enum colors { salmon, .... };
enum fishes { salmon, .... };

// This, however, works seamlessly.
struct colors_def { enum type { salmon, .... }; };
struct fishes_def { enum type { salmon, .... }; };

typedef typesafe_enum<colors_def> colors;
typedef typesafe_enum<fishes_def> fishes;

The only "hole" that solution provides is that it doesn't address the fact that it doesn't prevent enums of different types (or an enum and an int) from being directly compared, because when you use values directly you force the implicit conversion to int:

if (colors::salmon == fishes::salmon) { .../* Ooops! */... }

But so far I've found such problems can be solved by simply offering a better comparison to the compiler - for example, explicitly providing an operator that compares any two different enum types, then forcing it to fail:

// I'm using backports of C++11 utilities like static_assert and enable_if
template <typename Enum1, typename Enum2>
typename enable_if< (is_enum<Enum1>::value && is_enum<Enum2>::value) && (false == is_same<Enum1,Enum2>::value) , bool >
::type operator== (Enum1, Enum2) {
    static_assert (false, "Comparing enumerations of different types!");
}

Though it doesn't seem to break code so far, and it does to explicitly deal with the specific problem without doing something else, I'm not sure it such thing is a thing one "should" do (I suspect it will interfere with enums already taking part in conversion operators declared elsewhere; I'd gladly receive commentary about this).

Combining this with the above typesafe idiom gives something that is relatively close to C++11 enum class in humanibility (readability and maintainability) without having to do anything too obscure. And I have to admit it was fun to do, I had never thought to actually ask the compiler if I was dealing with enums or not...

谜兔 2024-07-15 06:19:55

我认为 Java enum 将是一个值得遵循的很好的模型。 本质上,Java 形式如下所示:

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Java 方法的有趣之处在于 OKCANCELResult 的不可变的单例实例(使用您所看到的方法)。 您无法再创建任何 Result 实例。 由于它们是单例,因此您可以通过指针/引用进行比较——非常方便。 :-)

ETA:在 Java 中,不是手动执行位掩码,而是使用 EnumSet 来指定位集(它实现 Set 接口,并且像集合一样工作) ---但使用位掩码实现)。 比手写的位掩码操作更具可读性!

I think the Java enum would be a good model to follow. Essentially, the Java form would look like this:

public enum Result {
    OK("OK"), CANCEL("Cancel");

    private final String name;

    Result(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

What's interesting about the Java approach is that OK and CANCEL are immutable, singleton instances of Result (with the methods that you see). You cannot create any further instances of Result. Since they're singletons, you can compare by pointer/reference---very handy. :-)

ETA: In Java, instead of doing bitmasks by hand, instead you use an EnumSet to specify a bit set (it implements the Set interface, and works like sets---but implemented using bitmasks). Much more readable than hand-written bitmask manipulation!

南烟 2024-07-15 06:19:55

我对此给出了答案这里< /a>,关于不同的主题。 这是一种不同风格的方法,它允许大多数相同的功能,而无需修改原始枚举定义(因此允许在未定义枚举的情况下使用)。 它还允许运行时范围检查。

我的方法的缺点是它不能以编程方式强制枚举和帮助器类之间的耦合,因此它们必须并行更新。 它对我有用,但是YMMV。

I gave an answer to this here, on a different topic. It's a different style of approach which allows most of the same functionality without requiring modification to the original enum definition (and consequently allowing usage in cases where you don't define the enum). It also allows runtime range checking.

The downside of my approach is that it doesn't programmatically enforce the coupling between the enum and the helper class, so they have to be updated in parallel. It works for me, but YMMV.

猫卆 2024-07-15 06:19:55

我目前正在编写自己的类型安全枚举库 https://bitbucket.org/chopsii/typesafe-enums

我不是最有经验的 C++ 开发人员,但我写这篇文章是因为 BOOST 库枚举的缺点。

您可以随意检查并自己使用它们,但它们存在一些(希望是轻微的)可用性问题,并且可能根本不跨平台。

如果您愿意,请做出贡献。 这是我的第一个开源项目。

I am currently writing my own typesafe enum library at https://bitbucket.org/chopsii/typesafe-enums

I am not the most experienced C++ developer ever, but I am writing this due to the shortcomings of the BOOST vault enums.

Feel free to check it out and use them yourself, but they have some (hopefully minor) usability issues, and are probably not at all cross-platform.

Please contribute if you want to. This is my first open source undertaking.

枯寂 2024-07-15 06:19:55

使用boost::variant

在尝试了很多上述想法并发现它们缺乏之后,我想到了这个简单的方法:

#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> BC;

void ab(const AB & e)
{
  if(isA(e))
    std::cerr << "A!" << std::endl;
  if(isB(e))
    std::cerr << "B!" << std::endl;
  // ERROR:
  // if(isC(e))
  //   std::cerr << "C!" << std::endl;

  // ERROR:
  // if(e == 0)
  //   std::cerr << "B!" << std::endl;
}

void bc(const BC & e)
{
  // ERROR:
  // if(isA(e))
  //   std::cerr << "A!" << std::endl;

  if(isB(e))
    std::cerr << "B!" << std::endl;
  if(isC(e))
    std::cerr << "C!" << std::endl;
}

int main() {
  AB a;
  a = A;
  AB b;
  b = B;
  ab(a);
  ab(b);
  ab(A);
  ab(B);
  // ab(C); // ERROR
  // bc(A); // ERROR
  bc(B);
  bc(C);
}

您可能可以想出一个宏来生成样板文件。 (如果您这样做,请告诉我。)

与其他方法不同,这种方法实际上是类型安全的,并且适用于旧的 C++。 例如,您甚至可以创建像 boost::variant 这样很酷的类型,来表示可以是 A、B、整数或什么都不是的值。几乎是 Haskell98 级别的类型安全。

需要注意的缺点:

  • 至少对于旧的 bo​​ost - 我使用的是 boost 1.33 的系统 - 你的变体中最多只能有 20 个项目; 有一个解决方法,但是
  • 会影响编译时间,
  • 出现疯狂的错误消息 - 但这是适合您的 C++

更新

这里,为了方便起见,您的类型安全枚举“库”。 粘贴此标头:

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#define ITEM(NAME, VAL) \
struct NAME##_t { \
  std::string toStr() const { return std::string( #NAME ); } \
  int toInt() const { return VAL; } \
}; \
static const NAME##_t NAME = NAME##_t(); \
template <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toInt_visitor(), a);
}

#define ENUM(...) \
typedef boost::variant<__VA_ARGS__>
#endif

并像这样使用它:

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

请注意,您必须在 ENUM 宏中说 A_t 而不是 A,这会破坏一些魔力。 那好吧。 另外,请注意现在有一个 toStr 函数和一个 toInt 函数来满足 OP 简单转换为字符串和整数的要求。 我无法弄清楚的要求是一种迭代项目的方法。 如果您知道如何写这样的东西,请告诉我。

Use boost::variant!

After trying a lot of the above ideas and finding them lacking I hit upon this simple approach:

#include <iostream>
#include <boost/variant.hpp>

struct A_t {};
static const A_t A = A_t();
template <typename T>
bool isA(const T & x) { if(boost::get<A_t>(&x)) return true; return false; }

struct B_t {};
static const B_t B = B_t();
template <typename T>
bool isB(const T & x) { if(boost::get<B_t>(&x)) return true; return false; }

struct C_t {};
static const C_t C = C_t();
template <typename T>
bool isC(const T & x) { if(boost::get<C_t>(&x)) return true; return false; }

typedef boost::variant<A_t, B_t> AB;
typedef boost::variant<B_t, C_t> BC;

void ab(const AB & e)
{
  if(isA(e))
    std::cerr << "A!" << std::endl;
  if(isB(e))
    std::cerr << "B!" << std::endl;
  // ERROR:
  // if(isC(e))
  //   std::cerr << "C!" << std::endl;

  // ERROR:
  // if(e == 0)
  //   std::cerr << "B!" << std::endl;
}

void bc(const BC & e)
{
  // ERROR:
  // if(isA(e))
  //   std::cerr << "A!" << std::endl;

  if(isB(e))
    std::cerr << "B!" << std::endl;
  if(isC(e))
    std::cerr << "C!" << std::endl;
}

int main() {
  AB a;
  a = A;
  AB b;
  b = B;
  ab(a);
  ab(b);
  ab(A);
  ab(B);
  // ab(C); // ERROR
  // bc(A); // ERROR
  bc(B);
  bc(C);
}

You can probably come up with a macro to generate the boilerplate. (Let me know if you do.)

Unlike other approaches this one is actually type-safe and works with old C++. You can even make cool types like boost::variant<int, A_t, B_t, boost::none>, for example, to represent a value that could be A, B, an integer or nothing which is almost Haskell98 levels of type safety.

Downsides to be aware of:

  • at-least with old boost -- I'm on a system with boost 1.33 -- you are limited to 20 items in your variant; there is a work-around however
  • affects compile time
  • insane error messages -- but that's C++ for you

Update

Here, for your convenience is your typesafe-enum "library". Paste this header:

#ifndef _TYPESAFE_ENUMS_H
#define _TYPESAFE_ENUMS_H
#include <string>
#include <boost/variant.hpp>

#define ITEM(NAME, VAL) \
struct NAME##_t { \
  std::string toStr() const { return std::string( #NAME ); } \
  int toInt() const { return VAL; } \
}; \
static const NAME##_t NAME = NAME##_t(); \
template <typename T> \
bool is##NAME(const T & x) { if(boost::get<NAME##_t>(&x)) return true; return false; } \


class toStr_visitor: public boost::static_visitor<std::string> {
public:
  template<typename T>
  std::string operator()(const T & a) const {
    return a.toStr();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
std::string toStr(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toStr_visitor(), a);
}

class toInt_visitor: public boost::static_visitor<int> {
public:
  template<typename T>
  int operator()(const T & a) const {
    return a.toInt();
  }
};

template<BOOST_VARIANT_ENUM_PARAMS(typename T)>
inline static
int toInt(const boost::variant<BOOST_VARIANT_ENUM_PARAMS(T)> & a) {
  return boost::apply_visitor(toInt_visitor(), a);
}

#define ENUM(...) \
typedef boost::variant<__VA_ARGS__>
#endif

And use it like:

ITEM(A, 0);
ITEM(B, 1);
ITEM(C, 2);

ENUM(A_t, B_t) AB;
ENUM(B_t, C_t) BC;

Notice you have to say A_t instead of A in the ENUM macro which destroys some of the magic. Oh well. Also, notice there's now a toStr function and a toInt function to meet OPs requirement of simple conversion to strings and ints. The requirement I can't figure out is a way to iterate over the items. Let me know if you know how to write such a thing.

古镇旧梦 2024-07-15 06:19:55

不确定这篇文章是否太晚了,但 GameDev.net 上有一篇文章满足了除第五点(迭代枚举器的能力)之外的所有要求:
http://www.gamedev.net/reference/snippets/features/cppstringizing/< /a>

本文描述的方法允许对现有枚举提供字符串转换支持,而无需更改其代码。 如果您只想支持新的枚举,我会选择 Boost.Enum (上面提到的)。

Not sure if this post is too late, but there's an article on GameDev.net which satisfies all but the 5th point (ability to iterate over enumerators):
http://www.gamedev.net/reference/snippets/features/cppstringizing/

The method described by the article allows string conversion support for existing enumerations without changing their code. If you only want support for new enumerations though, I'd go with Boost.Enum (mentioned above).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文