从可变参数创建枚举?

huangapple go评论142阅读模式
英文:

Creating enum from variadic arguments?

问题

在其他一些编程语言中,你可以同时指定枚举和状态,例如:

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);
   ...

这在C++中并不难模拟,但会变得有些冗长。

考虑以下“手写”的方法:

#pragma warning(error : 4062)    

enum tvtype {
    black_and_white,
    color
};    

struct TvType {
    tvtype tvtype_;
    constexpr TvType(tvtype type) : tvtype_{type} {

    }
    constexpr operator tvtype() const {
        return tvtype_;
    }
};

constexpr TvType BlackAndWhite(black_and_white);
constexpr TvType Color(color);

bool hasColor(TvType tv) {
    switch (tv) {
        case BlackAndWhite:
        return false;
        //case Color:
        //return true;
    };

    return false;
}

使用这种方法可以很好地工作,只是有点冗长。

使用这种方法,现在可以存储一些额外的信息,例如枚举值的字符串表示形式,可能还可以添加函数等等。

另外,因为它实际上是一个枚举,在switch语句中编译器可以检查所有情况是否已被检查(上面的示例由于已注释掉了'color'情况而生成了编译错误)。

我对以一种通用/模板化的方式获取生成的enum或类似满足编译器检查的switch和可扩展性的方法感兴趣。

但是,为了使上面的代码模板化,似乎应该以一种(编译时)可编程的方式生成枚举,这引出了以下问题:

是否可以从折叠表达式(fold expression)或者其他递归模板的方式创建枚举,或者有其他方法可以实现类似的功能?

英文:

In some other langauges you can specify enums along with states, eg.:

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);
   ...

This isn't hard to emulate in [tag:c++] - but only it easily becomes somewhat verbose.

I know this has been asked before and similar questions exists, e.g. in https://stackoverflow.com/questions/18996403/build-an-enum-using-variadic-template-parameters, but I wanted to ask this again and have slightly different perspective.

Consider the following 'hand-written' approach:

#pragma warning(error : 4062)    

enum tvtype {
    black_and_white,
    color
};    

struct TvType {
    tvtype tvtype_;
    constexpr TvType(tvtype type) : tvtype_{type} {

    }
    constexpr operator tvtype() const {
        return tvtype_;
    }
};

constexpr TvType BlackAndWhite(black_and_white);
constexpr TvType Color(color);

bool hasColor(TvType tv) {
    switch (tv) {
        case BlackAndWhite:
        return false;
        //case Color:
        //return true;
    };

    return false;
}

Try it yourself (comment in the two lines in the switch to make it compile)

This method is working well - only it is somewhat verbose.

Using this method it should now be possible to store some extra information along with the enum value, like a string representation, perhaps adding functions as well, etc.

Also because it's actually an enum underneath, it's possible for the compiler to check that all cases have been checked in the switch (the example above should generates a compile error due that the 'color' case is outcommented).

I'm interested in a generic / templated way of getting the enum generated or something similar that fulfills the requirement of compiler checked switch and extensibility.

But in order to templatize the above, it seems the enum should be generated in a (compile-time) programatical way, which leads to the question:

Is it possible to create an enum from a fold expression for instance or failing that from some kind of recursive template ?

答案1

得分: 1

不能使用可变参数模板创建枚举。模板参数可以是类型或常量值,无法用符号替代它并将其放入enum块中。

但是,你可以使用宏来实现这一点。唯一的问题是,虽然C++11有一些称为"可变宏"的东西,但它非常有限。

#define MY_MACRO(...) enum { __VA_ARGS__ }

你无法对可变参数进行任何转换,就像你可以使用模板进行的那样,它只是简单地用...代表的所有标记替换__VA_ARGS___

但是,使用这个很棒的答案中的技术:https://stackoverflow.com/a/11763277/1863938,你可以支持"最多N个"可变解决方案。使用这个技术,你可以创建一个模板,用于处理最多N个值的样板代码。以下是最多支持4个符号的示例:

#define _JENUM_GET_MACRO(_1,_2,_3,_4,NAME,...) NAME

#define _JENUM_VALUE_DEF1(_1) _##_1##_v_
#define _JENUM_VALUE_DEF2(_1,_2) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2)
#define _JENUM_VALUE_DEF3(_1,_2,_3) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2), _JENUM_VALUE_DEF1(3)
#define _JENUM_VALUE_DEF4(_1,_2,_3,_4) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2), _JENUM_VALUE_DEF1(3), _JENUM_VALUE_DEF1(_4)
#define _JENUM_VALUE_DEF(...) _JENUM_GET_MACRO(__VA_ARGS__, _JENUM_VALUE_DEF4, _JENUM_VALUE_DEF3, _JENUM_VALUE_DEF2, _JENUM_VALUE_DEF1)(__VA_ARGS__)
#define _JENUM_ENUM_DEF(name, ...) enum _##name##_e_ { _JENUM_VALUE_DEF(__VA_ARGS__) };

#define _JENUM_CLASS_DEF(name) struct name {\
    _##name##_e_ _value;\
    constexpr name(const name& that) : _value(that._value) { }\
    constexpr name(_##name##_e_ _value) : _value(_value) { }\
    constexpr operator _##name##_e_() const { return _value; }\
};\

#define _JENUM_CONST_DEF1(name, _1) constexpr name _1(_JENUM_VALUE_DEF1(_1));
#define _JENUM_CONST_DEF2(name, _1,_2) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2)
#define _JENUM_CONST_DEF3(name, _1,_2,_3) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2) _JENUM_CONST_DEF1(name, _3)
#define _JENUM_CONST_DEF4(name, _1,_2,_3,_4) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2) _JENUM_CONST_DEF1(name, _3) _JENUM_CONST_DEF1(name, _4)
#define _JENUM_CONST_DEF(name, ...) _JENUM_GET_MACRO(__VA_ARGS__, _JENUM_CONST_DEF4, _JENUM_CONST_DEF3, _JENUM_CONST_DEF2, _JENUM_CONST_DEF1)(name, __VA_ARGS__)

#define JENUM(name, ...)\
    _JENUM_ENUM_DEF(name, __VA_ARGS__)\
    _JENUM_CLASS_DEF(name)\
    _JENUM_CONST_DEF(name, __VA_ARGS__)

... 只需按照该模式添加更多的重载。

然后,使用如下方式:

JENUM(TvType, BlackAndWhite, Color);

现在,如果你想要添加方法等内容,更容易的方法是继承而不是尝试将任何内容集成到宏中:

struct TvTypeEx : public TvType {
    using TvType::TvType;
    TvTypeEx(TvType that) : TvType(that) { }
    bool hasColor() const { return *this == Color; }
};

现在你可以像这样做一些事情:

TvTypeEx tv = Color;
return tv.hasColor() ? GetColorContent() : GetBlackAndWhiteContent();

演示:https://godbolt.org/z/rdqS65

英文:

> Is it possible to create an enum from a fold expression for instance or failing that from some kind of recursive template ?

Not with variadic templates. Template arguments can be types, or constant values. You can't substitute a symbol to stick it inside an enum block.

However, you can do that with macros. Only trouble is, while C++11 has something called "variadic macros", it's very limited.

#define MY_MACRO(...) enum { __VA_ARGS__ }

You can't do any kind of transformation on variadic arguments like you can with templates, it literally just replaces __VA_ARGS___ with all the tokens the ... represents.

But, using the technique from this great answer: https://stackoverflow.com/a/11763277/1863938 , you can support "up to N" variadic solutions. Using that, you can create a macro that templates your boilerplate class for up to N values. Below is for up to 4 symbols:

#define _JENUM_GET_MACRO(_1,_2,_3,_4,NAME,...) NAME

#define _JENUM_VALUE_DEF1(_1) _##_1##_v_
#define _JENUM_VALUE_DEF2(_1,_2) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2)
#define _JENUM_VALUE_DEF3(_1,_2,_3) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2), _JENUM_VALUE_DEF1(3)
#define _JENUM_VALUE_DEF4(_1,_2,_3,_4) _JENUM_VALUE_DEF1(_1), _JENUM_VALUE_DEF1(_2), _JENUM_VALUE_DEF1(3), _JENUM_VALUE_DEF1(_4)
#define _JENUM_VALUE_DEF(...) _JENUM_GET_MACRO(__VA_ARGS__, _JENUM_VALUE_DEF4, _JENUM_VALUE_DEF3, _JENUM_VALUE_DEF2, _JENUM_VALUE_DEF1)(__VA_ARGS__)
#define _JENUM_ENUM_DEF(name, ...) enum _##name##_e_ { _JENUM_VALUE_DEF(__VA_ARGS__) };

#define _JENUM_CLASS_DEF(name) struct name {\
    _##name##_e_ _value;\
    constexpr name(const name& that) : _value(that._value) { }\
    constexpr name(_##name##_e_ _value) : _value(_value) { }\
    constexpr operator _##name##_e_() const { return _value; }\
};\

#define _JENUM_CONST_DEF1(name, _1) constexpr name _1(_JENUM_VALUE_DEF1(_1));
#define _JENUM_CONST_DEF2(name, _1,_2) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2)
#define _JENUM_CONST_DEF3(name, _1,_2,_3) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2) _JENUM_CONST_DEF1(name, _3)
#define _JENUM_CONST_DEF4(name, _1,_2,_3,_4) _JENUM_CONST_DEF1(name, _1) _JENUM_CONST_DEF1(name, _2) _JENUM_CONST_DEF1(name, _3) _JENUM_CONST_DEF1(name, _4)
#define _JENUM_CONST_DEF(name, ...) _JENUM_GET_MACRO(__VA_ARGS__, _JENUM_CONST_DEF4, _JENUM_CONST_DEF3, _JENUM_CONST_DEF2, _JENUM_CONST_DEF1)(name, __VA_ARGS__)

#define JENUM(name, ...)\
    _JENUM_ENUM_DEF(name, __VA_ARGS__)\
    _JENUM_CLASS_DEF(name)\
    _JENUM_CONST_DEF(name, __VA_ARGS__)

... just follow the pattern to add more overloads

Then, to use:

JENUM(TvType, BlackAndWhite, Color);

Now, if you want to add methods and whatnot, it's easier to just inherit than to try and work anything into the macro:

struct TvTypeEx : public TvType {
    using TvType::TvType;
    TvTypeEx(TvType that) : TvType(that) { }
    bool hasColor() const { return *this == Color; }
};

Now you can do stuff like:

TvTypeEx tv = Color;
return tv.hasColor() ? GetColorContent() : GetBlackAndWhiteContent();

Demo: https://godbolt.org/z/rdqS65

huangapple
  • 本文由 发表于 2020年1月3日 18:56:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/59577322.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定