Can a C++ enum class have methods?
Asked Answered
D

9

244

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 enum class instead of enums).

http://www.cplusplus.com/doc/tutorial/other_data_types/ doesn't mention anything about methods However, I was under the impression that any type of class can have methods.

Disburse answered 22/1, 2014 at 23:4 Comment(8)
No, it cannot. See here.Aldoaldol
@octavian Note my answer and rethink about your use cases please!Presidium
@πάνταῥεῖ you're totally right, I've read enum but thought union, killed the comment.Payload
@octavian Are you even asking for a particular use case at all, or did you just want to have the standards restrictions on c++11 enum class/struct confirmed?Presidium
I had a use in mind ... and this was the fundamental issueDisburse
@πάνταῥεῖ what was the point of this edit ?Hifi
@Thomas Can't remember actually, probably to be able to change my initial voting decision (I know that's kind of misusing).Presidium
In the 4th Ed of Stroustrup's book The C++ Programming Language, he says, "By default, an enum class has only assignment, initialization, and comparisons (e.g. == and < ) defined. However, an enumeration is a user-defined type so we can define operators for it." He then defines the operator++ for an enum class So you can define operators for enum classes at least.Cletis
C
177

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.

Cowes answered 22/1, 2014 at 23:9 Comment(4)
On ##C++ I was told that "c++ aims to be as confusing and expert friendly as possible". Obviously it's a joke, but you get the idea :)Cowes
A union is not what John Doe would consider a class, too. Yet they can have member functions. And classes are really not mandatory for member functions. Using a designator like value or this, something like enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; } (here also allowing ;), it can make just as much sense as other forms of functions.Proteinase
Why wasn't this struct Foo{ enum {BAR, BAZ}; }; and therefore enum struct?Classic
Oh, enum struct is actually a valid way to create a scoped enum! Who knew!Classic
D
203

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:
  // Allow switch and comparisons.
  constexpr operator Value() const { return value; }

  // Prevent usage: if(fruit)
  explicit operator bool() const = delete;        
#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.

Dianetics answered 13/11, 2018 at 15:13 Comment(11)
Shouldn't be also IsYellow(), operator==, != marked as constexpr?Montano
I'm getting " error: missing binary operator before token "switch" "Lindo
This is really smart. Is there a way for the enum inside the class to also be an enum class?Fluoridate
Could you elaborate on the macro if-else part?Stellular
It should be explicit operator bool() const = delete;, otherwise if(fruit) is possible if your fruit is of type const Fruit.Eaglet
Why enum Value cannot be declared as enum class Value? Comparisons do not compile with enum class.Arnaldo
why did you include that precompiler #if?Armored
enum class Value would mean you have to write Fruit::Value::Strawberry.Dianetics
The precompiler #if is because you can make a choice on if you want to enable the usage or not. Having automatic conversions to Value() if you don't need it is imho a chance to start using the enum in a way that it shouldn't be.Dianetics
Doesn't compile for me, says error: missing binary operator before token "switch". I know #ifdefbut what are all these things? #if Enable switch(fruit) use case:Swisher
Ok, switch(fruit) use case: is actually just a comment, allowed by some compilers for some reason... so you'd just write e.g. #define Enabled 1. Actually just delete the preprocessor lines and the #else block.Swisher
C
177

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.

Cowes answered 22/1, 2014 at 23:9 Comment(4)
On ##C++ I was told that "c++ aims to be as confusing and expert friendly as possible". Obviously it's a joke, but you get the idea :)Cowes
A union is not what John Doe would consider a class, too. Yet they can have member functions. And classes are really not mandatory for member functions. Using a designator like value or this, something like enum Size { Huge, Mega, Apocalypse; bool operator<(X rhs) const { return *this < rhs; } (here also allowing ;), it can make just as much sense as other forms of functions.Proteinase
Why wasn't this struct Foo{ enum {BAR, BAZ}; }; and therefore enum struct?Classic
Oh, enum struct is actually a valid way to create a scoped enum! Who knew!Classic
W
21

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 "";
    }
};
Weka answered 18/2, 2017 at 21:52 Comment(0)
R
7

There is a pretty compatible ability(§) to refactor an enum into a class without having to rewrite your code, which means that effectively you can do what you were asking to do without too much editing.

(§) as ElementW points out in a comment, type_traits dependent code will not work, so e.g. one cannot use auto, etc. There may be some way of handling such stuff, but in the end one is converting an enum into a class, and it is always a mistake to subvert C++

the enum struct and enum class specifications are about scoping so not part of this.

Your original enum is e.g. 'pet' (this is as an example only!).

enum pet { 
    fish, cat, dog, bird, rabbit, other 
};

(1) You modify that to eg petEnum (so as to hide it from your existing code).

enum petEnum { 
    fish, cat, dog, bird, rabbit, other 
};

(2) You add a new class declaration below it (named with the original enum)

class pet {
    private:
        petEnum value;
        pet() {}

    public:
        pet(const petEnum& v) : value{v} {} //not explicit here.
        operator petEnum() const { return value; }
        pet& operator=(petEnum v) { value = v; return *this;}
        bool operator==(const petEnum v) const { return value == v; }
        bool operator!=(const petEnum v) const { return value != v; }
 //     operator std::string() const;

};

(3) You can now add whatever class methods you like to your pet class. eg. a string operator

    pet::operator std::string() const {
        switch (value) {
            case fish: return "fish";
            case cat:  return "cat";
            case dog:  return "dog";
            case bird: return "bird";
            case rabbit: return "rabbit";
            case other: return "Wow. How exotic of you!";
        }
    }

Now you can use eg std::cout...

int main() {
    pet myPet = rabbit;
    if(myPet != fish) {
        cout << "No splashing! ";
    }
    std::cout << "I have a " << std::string(myPet) << std::endl;
    return 0;
}
Reasonable answered 14/3, 2019 at 8:56 Comment(4)
It is not fully compatible: if you use the enum values with any kind of type deduction where it is expected to get a pet typename/instance, be it templates, auto, or decltype, this breaks, as you get a petEnum instead.Turquoise
By wrapping it in a class you lose the ability to use it for switch-case statements - one of the things were enums normally would really shine.Magnetics
@ABaumstumpf, sure. But the Q is about enums with methods. Needless to say if one is wishing to switch on an instance, the switch would be handled as a method within the class, cf. my example 3 above.Reasonable
@Reasonable It is an alternative that allows you to have names and functions, but it lacks many of the traits of enums - implicit/explicit conversions to underlying type and operators need to be implemented, switch-statements need Constant-expressions etc. If you don#t need those features of enums then a class works just fine.Magnetics
B
4

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 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.

Brabant answered 22/1, 2014 at 23:18 Comment(2)
There are more use cases that warrant methods on enum classe, e.g. toString() and fromString(). Every (even not so) modern major language has this (e.g. C#, Java, Swift), just not C++.Taipan
Let's hope for unified call syntax next time around... open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdfPica
B
3

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"
Borden answered 10/5, 2020 at 6:36 Comment(0)
U
2

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"); }
Uncovenanted answered 30/9, 2020 at 12:58 Comment(0)
M
-1

Here is what I do. Hope it helps.

In addition to the enum, I create another calls extension with static members.

/// @brief Current type selector for Nano only (Merlin)
enum class CurrentTypeSelector {
    /// Force DC (Direct current)
    currentTypeSelectorForceDc = 0,

    /// Force PC (pulsed current)
    currentTypeSelectorForcePc = 1,

    /// Automatic mode: select the one sent by the batteries
    currentTypeSelectorAutomatic = 2,

    /// Force shunt current
    currentTypeSelectorShunt = 3,

    /// Pick either DC or PC, aka what the battery has chosen
    currentTypeSelectorBatteryAutomatic = 4,
};

// CurrentTypeSelector enum extension
struct CurrentTypeSelectorExtension {

    static CurrentTypeSelector fromInt(int v) {
        switch (v) {
        case (unsigned)CurrentTypeSelector::currentTypeSelectorForceDc:             return CurrentTypeSelector::currentTypeSelectorForceDc;
        case (unsigned)CurrentTypeSelector::currentTypeSelectorForcePc:             return CurrentTypeSelector::currentTypeSelectorForcePc;
        case (unsigned)CurrentTypeSelector::currentTypeSelectorShunt:               return CurrentTypeSelector::currentTypeSelectorShunt;
        case (unsigned)CurrentTypeSelector::currentTypeSelectorBatteryAutomatic:    return CurrentTypeSelector::currentTypeSelectorBatteryAutomatic;
        case (unsigned)CurrentTypeSelector::currentTypeSelectorAutomatic:           return CurrentTypeSelector::currentTypeSelectorAutomatic;
        default: 
            pepsr.alert("CurrentTypeSelectorEnum::fromValue: Unknown value %d, returns DC", v);
            return CurrentTypeSelector::currentTypeSelectorForceDc;
        }
    }

    static String toString(CurrentTypeSelector v) {

        // if (v >= std::numeric_limits<enum CurrentTypeSelector>::max()) {
        //  pepsr.alert("CurrentTypeSelectorEnum::toString: CurrentTypeSelectorEnum has more values");
        // }
        switch (v) {
        case CurrentTypeSelector::currentTypeSelectorForceDc: return "dc";
        case CurrentTypeSelector::currentTypeSelectorForcePc: return "pc";
        case CurrentTypeSelector::currentTypeSelectorShunt: return "shunt";
        case CurrentTypeSelector::currentTypeSelectorBatteryAutomatic: return "battery";
        case CurrentTypeSelector::currentTypeSelectorAutomatic: return "auto";

        default: 
            pepsr.alert("CurrentTypeSelectorEnum::toString: Unknown. CurrentTypeSelectorEnum has more values npow. You must upgrade toString()");
            return "pc";
        }
    }
};
Mariannemariano answered 7/3, 2024 at 13:45 Comment(0)
G
-3

Yes, they can, but you need to make a wrapper class, for example:

#include <iostream>
using namespace std;

class Selection {
    public: 
        enum SelectionEnum {
            yes,
            maybe,
            iDontKnow,
            canYouRepeatTheQuestion
        };
        Selection(SelectionEnum selection){value=selection;};
        string toString() {
            string selectionToString[4]={
                "Yes",
                "Maybe",
                "I don't know",
                "Can you repeat the question?"
            };
            return selectionToString[value];
        };
    private:
        SelectionEnum value;
};


int main(){
    Selection s=Selection(Selection::yes);
    cout<<s.toString()<<endl;
    return 0;
}
Glenglencoe answered 21/12, 2022 at 13:27 Comment(1)
This was already suggested 4 years ago.Erwin

© 2022 - 2025 — McMap. All rights reserved.