依靠枚举 C++ 自动

2021-12-30 00:00:00 enums count c++

我在用 C++ 编写枚举时发现了一种模式.是这样的:

I've come to a pattern when writing enums in C++. It is like this:

class Player
{
public:
    class State
    {
    public:
        typedef enum
        {
            Stopped, 
            Playing, 
            Paused
        }PossibleValues;  

        static const int Count() {return Paused+1;};
        static const PossibleValues Default() {return Stopped;};
    };

    //...
}

这解决了枚举的一些常见问题,例如外部命名空间的污染等.但还有一点我不喜欢:Count() 是手动完成的.我知道的方法只有两种:一种是从 Last+1 计算出来的;或编写普通的硬编码.

This solves a some of the usual issues with enums, like pollution of outside namespaces, etc. But there is still a thing I don't like: The Count() is done manually. There are only two ways I know how to do it: this one is calculated from Last+1; or write plain hardcoded.

问题是:有没有办法,比如使用预处理器宏,自动获取计数,将其放在 Count() 方法之后?注意:我不想在枚举中有最后一个叫做 Count 的假元素,污染它!

Question is: Is there some way, like using preprocessor macros, that automatically gets the count, to put it after in the Count() method? Attention: I don't want to have a last fake element called Count inside the enum, polluting it!

提前致谢!

更新 1:

有一个关于 实施的有趣讨论标准 C++11 中的 N4428 枚举反射(部分) 用于更高级枚举的提议.

There is an interesting discussion on Implementation of N4428 enum reflection in standard C++11 (partial) for a proposal of more advanced enums.

更新 2:

有趣的文档 N4451-静态反射(修订版. 3) 关于 MetaEnums 和 MetaEnumClasses 的第 3.16、3.17、A.7、A.8 节.

Interesting document N4451- Static reflection (rev. 3) on its sections 3.16, 3.17, A.7, A.8 about MetaEnums and MetaEnumClasses.

更新 3:

在我看到 https://bytes.com/topic/c/answers/127908-numeric_limits-specialization#post444962.如果枚举类的枚举器列表是连续整数,通过定义它的最大值和最小值,我们可以检查一个值是否属于它.

I came to another interesting pattern using an enum class, after I've seen https://bytes.com/topic/c/answers/127908-numeric_limits-specialization#post444962. If the enum class's enumerator list is continuously integer, by defining its maximum and its minimum, we can check whether a value belongs to it or not.

如果在 Player::State 上使用 Count() 方法的目的是检查枚举中是否有一个值,那么这个目的也达到了使用 numeric_limits 方法,甚至更胜一筹,因为它不需要枚举器列表以零值项目开头!

If the purpose of using the Count() method on the Player::State was to check if a value was in the enum, that purpose has also been achieved with the numeric_limits approach, and is even superior, as it is not required the enumerator list begins with a ZERO valued item!

enum class Drink
{
    Water,
    Beer,
    Wine,
    Juice,
};


#pragma push_macro("min")
#undef min

#pragma push_macro("max")
#undef max

namespace std
{
    template <> class numeric_limits < Drink >
    {
    public:
        static const/*expr*/ bool is_specialized = true;

        static const/*expr*/ Drink min() /*noexcept*/ { return Drink::Water; }
        static const/*expr*/ Drink max() /*noexcept*/ { return Drink::Juice; }

        static const/*expr*/ Drink lowest() /*noexcept*/ { return Drink::Water; }

        static const/*expr*/ Drink default() /*noexcept*/ { return Drink::Beer; }
    };
}

#pragma pop_macro("min")
#pragma pop_macro("max")

使用案例:

来自应用程序的变量:

Drink m_drink;

在构造函数中初始化为:

which in constructor is initialized with:

m_drink = numeric_limits<Drink>::default();

关于一个表单的初始化,我可以这样做:

On the initialization of a form, I can do:

pComboDrink->SetCurSel(static_cast<int>(theApp.m_drink));

在它上面,为了使界面适应用户所做的更改,我可以使用作用域枚举类值进行切换:

On it, for adapting the interface to changes done by the user, I can do a switch with scoped enum class values:

switch (static_cast<Drink>(pComboDrink->GetCurSel()))
{
case Drink::Water:
case Drink::Juice:
    pAlcohoolDegreesControl->Hide();
break;

case Drink::Beer:
case Drink::Wine:
    pAlcohoolDegreesControl->Show();
break;

default:
    break;
}

在对话框的确认过程 (OnOK) 中,我可以检查该值是否超出边界,然后再将其保存到相应的应用程序变量中:

And on the dialog's confirmation procedure (OnOK), I can check if the value is out of boundaries, before saving it to the respective app var:

int ix= pComboDrink->GetCurSel();

if (ix == -1)
    return FALSE;

#pragma push_macro("min")
#undef min

#pragma push_macro("max")
#undef max

if (ix < static_cast<int> (std::numeric_limits<Drink>::min()) ||  ix > static_cast<int> (std::numeric_limits<Drink>::max()) )
    return FALSE;

#pragma pop_macro("min")
#pragma pop_macro("max")

theApp.m_drink= static_cast<Drink>(ix);

注意:

  1. 关键字constexpr(我注释了/*expr*/,将其保留为const)和noexcept被评论只是因为我使用的编译器 (Visual C++ 2013) 在当前版本中还不支持它们.
  2. 也许您不需要临时取消定义最小和最大宏的逻辑.
  3. 我知道 default() 不适合数字限制"范围;但它似乎是一个方便的地方;甚至它与 default 词重合,在某些情况下它是一个关键字!
  1. The keywords constexpr (I commented /*expr*/, leaving it as const ) and noexcept are commented only because the compiler I am using (Visual C++ 2013) does not support them yet at the current version.
  2. Maybe you do not need the logic to temporary undefine the min and max macros.
  3. I know that the default() does not fit on a "numeric limits" scope; but it seemed an handy place to put it on; even it coincides with the default word that in some contexts is a keyword!

推荐答案

重新发布来自类似问题的答案 (非序列整数 c++ 枚举的最佳方法是什么),因为它与其他方面非常相关未回答的问题.

Reposting an answer from a similar question (What is the best way for non sequencial integer c++ enums) because it was kind of relevant to an otherwise pretty much unanswered question.

一种可以用来获取所需内容的模式是使用 std::initializer_list 来存储枚举的所有值.

A pattern you could use to get what you want is to use an std::initializer_list to store all the values of your enum.

namespace PossibleValues
{
    enum Type
    {
        ZERO= 0,
        PLUS180= 180,
        PLUS90= 90,
        MINUS90= -90
    };

    constexpr auto Values = {ZERO, PLUS180, PLUS90, MINUS90};
    size_t Count() { return Values.size(); }
    Type Default() { return *begin(Values); }
}

这也有一个好处,即即使枚举值没有线性值,也可以迭代它们.

This also has the benefit of being able to iterate of the values of the enum even if they don't have linear values.

而且我认为您可以使用可变参数宏从单个宏生成枚举、初始化列表和函数,尽管在最好的情况下,这种事情应该在标准中.

And I think you could possibly generate both the enum, the initializer list and the functions from a single macro with a variadic macro, though in the best of worlds this kind of thing should be in the standard.

当我使用可能的值作为枚举或使用可能值的结构时,我的编译器会抱怨类型不完整.为枚举使用命名空间有点不寻常,但它工作正常.

When I used PossibleValues as an enum or used a struct for PossibleValues, my compiler would complain about incomplete type. Using a namespace for an enum is a bit unusual, but it works fine.

相关文章