Can a C++ enum class have methods?

前端 未结 7 766
抹茶落季
抹茶落季 2020-12-12 17:44

I have an enum class with two values, and I want to create a method which receives a value and returns the other one. I also want to maintain type safety(that\'s why I use e

相关标签:
7条回答
  • 2020-12-12 18:11

    Concentrating on the description of the question instead of the title a possible answer is

    struct LowLevelMouseEvent {
        enum Enum {
            mouse_event_uninitialized = -2000000000, // generate crash if try to use it uninitialized.
            mouse_event_unknown = 0,
            mouse_event_unimplemented,
            mouse_event_unnecessary,
            mouse_event_move,
            mouse_event_left_down,
            mouse_event_left_up,
            mouse_event_right_down,
            mouse_event_right_up,
            mouse_event_middle_down,
            mouse_event_middle_up,
            mouse_event_wheel
        };
        static const char* ToStr (const type::LowLevelMouseEvent::Enum& event)
        {
            switch (event) {
                case mouse_event_unknown:         return "unknown";
                case mouse_event_unimplemented:   return "unimplemented";
                case mouse_event_unnecessary:     return "unnecessary";
                case mouse_event_move:            return "move";
                case mouse_event_left_down:       return "left down";
                case mouse_event_left_up:         return "left up";
                case mouse_event_right_down:      return "right down";
                case mouse_event_right_up:        return "right up";
                case mouse_event_middle_down:     return "middle down";
                case mouse_event_middle_up:       return "middle up";
                case mouse_event_wheel:           return "wheel";
                default:
                    Assert (false);
                    break;
            }
            return "";
        }
    };
    
    0 讨论(0)
  • 2020-12-12 18:21

    No, they can't.

    I can understand that the enum class part for strongly typed enums in C++11 might seem to imply that your enum has class traits too, but it's not the case. My educated guess is that the choice of the keywords was inspired by the pattern we used before C++11 to get scoped enums:

    class Foo {
    public:
      enum {BAR, BAZ};
    };
    

    However, that's just syntax. Again, enum class is not a class.

    0 讨论(0)
  • 2020-12-12 18:23

    It may not fulfill all your needs, but with non-member operators you can still have a lot of fun. For example:

    #include <iostream>
    
    enum class security_level
    {
        none, low, medium, high
    };
    
    static bool operator!(security_level s) { return s == security_level::none; }
    
    static security_level& operator++(security_level& s)
    {
        switch(s)
        {
            case security_level::none: s = security_level::low; break;
            case security_level::low: s = security_level::medium; break;
            case security_level::medium: s = security_level::high; break;
            case security_level::high: break;
        }
        return s;
    }
    
    static std::ostream & operator<<(std::ostream &o, security_level s)
    {
        switch(s)
        {
            case security_level::none: return o << "none";
            case security_level::low: return o << "low";
            case security_level::medium: return o << "medium";
            case security_level::high: return o << "high";
        }
    }
    

    This allows code like

    security_level l = security_level::none;   
    if(!!l) { std::cout << "has a security level: " << l << std::endl; } // not reached
    ++++l;
    if(!!l) { std::cout << "has a security level: " << l << std::endl; } // reached: "medium"
    
    0 讨论(0)
  • 2020-12-12 18:29

    As mentioned in the other answer, no. Even enum class isn't a class.


    Usually the need to have methods for an enum results from the reason that it's not a regular (just incrementing) enum, but kind of bitwise definition of values to be masked or need other bit-arithmetic operations:

    enum class Flags : unsigned char {
        Flag1 = 0x01 , // Bit #0
        Flag2 = 0x02 , // Bit #1
        Flag3 = 0x04 , // Bit #3
        // aso ...
    }
    
    // Sets both lower bits
    unsigned char flags = (unsigned char)(Flags::Flag1 | Flags::Flag2);
    
    // Set Flag3
    flags |= Flags::Flag3;
    
    // Reset Flag2
    flags &= ~Flags::Flag2;
    

    Obviously one thinks of encapsulating the necessary operations to re-/set single/group of bits, by e.g. bit mask value or even bit index driven operations would be useful for manipulation of such a set of 'flags'.

    The c++11 struct/class specification just supports better scoping of enum values for access. No more, no less!

    Ways to get out of the restriction you cannot declare methods for enum (classes) are , either to use a std::bitset (wrapper class), or a bitfield union.

    unions, and such bitfield unions can have methods (see here for the restrictions!).

    I have a sample, how to convert bit mask values (as shown above) to their corresponding bit indices, that can be used along a std::bitset here: BitIndexConverter.hpp
    I've found this pretty useful for enhancing readability of some 'flag' decison based algorithms.

    0 讨论(0)
  • 2020-12-12 18:30

    Based on jtlim's answer

    Idea (Solution)

    enum ErrorType: int {
      noConnection,
      noMemory
    };
    
    class Error {
    public:
      Error() = default;
      constexpr Error(ErrorType type) : type(type) { }
    
      operator ErrorType() const { return type; }
      constexpr bool operator == (Error error) const { return type == error.type; }
      constexpr bool operator != (Error error) const { return type != error.type; }    
      constexpr bool operator == (ErrorType errorType) const { return type == errorType; }
      constexpr bool operator != (ErrorType errorType) const { return type != errorType; }
    
      String description() { 
        switch (type) {
        case noConnection: return "no connection";
        case noMemory: return "no memory";
        default: return "undefined error";
        }
     }
    
    private:
      ErrorType type;
    };
    

    Usage

    Error err = Error(noConnection);
    err = noMemory;
    print("1 " + err.description());
    
    switch (err) {
      case noConnection: 
        print("2 bad connection");
        break;
      case noMemory:
        print("2 disk is full");
        break;
      default: 
        print("2 oops");
        break;
    }
    
    if (err == noMemory) { print("3 Errors match"); }
    if (err != noConnection) { print("4 Errors don't match"); }
    
    0 讨论(0)
  • 2020-12-12 18:31

    While the answer that "you can't" is technically correct, I believe you may be able to achieve the behavior you're looking for using the following idea:

    I imagine that you want to write something like:

    Fruit f = Fruit::Strawberry;
    f.IsYellow();
    

    And you were hoping that the code looks something like this:

    enum class Fruit : uint8_t
    {
      Apple, 
      Pear,
      Banana,
      Strawberry,
    
      bool IsYellow() { return this == Banana; }
    };
    
    ...
    

    But of course, it doesn't work, because enums can't have methods (and 'this' doesn't mean anything in the above context)

    However, if you use the idea of a normal class containing a non-class enum and a single member variable that contains a value of that type, you can get extremely close to the syntax/behavior/type safety that you want. i.e.:

    class Fruit
    {
    public:
      enum Value : uint8_t
      {
        Apple,
        Pear,
        Banana,
        Strawberry
      };
    
      Fruit() = default;
      constexpr Fruit(Value aFruit) : value(aFruit) { }
    
    #if Enable switch(fruit) use case:
      operator Value() const { return value; }  // Allow switch and comparisons.
                                                // note: Putting constexpr here causes
                                                // clang to stop warning on incomplete
                                                // case handling.
      explicit operator bool() = delete;        // Prevent usage: if(fruit)
    #else
      constexpr bool operator==(Fruit a) const { return value == a.value; }
      constexpr bool operator!=(Fruit a) const { return value != a.value; }
    #endif
    
      constexpr bool IsYellow() const { return value == Banana; }
    
    private:
      Value value;
    };
    

    Now you can write:

    Fruit f = Fruit::Strawberry;
    f.IsYellow();
    

    And the compiler will prevent things like:

    Fruit f = 1;  // Compile time error.
    

    You could easily add methods such that:

    Fruit f("Apple");
    

    and

    f.ToString();
    

    can be supported.

    0 讨论(0)
提交回复
热议问题