Extending enums in C++?

Viewed 53911

Is there a way in C++ to extend/"inherit" enums?

I.E:

enum Enum {A,B,C};
enum EnumEx : public Enum {D,E,F};

or at least define a conversion between them?

12 Answers

I had this problem in some projects that ran on small hardware devices I design. There is a common project that holds a number of services. Some of these services use enums as parameters to get additional type checking and safety. I needed to be able to extend these enums in the projects that use these services.

As other people have mentioned c++ doesn't allow you to extend enums. You can however emulate enums using a namespace and a template that has all the benefits of enum class.

enum class has the following benefits:

  1. Converts to a known integer type.
  2. Is a value type
  3. Is constexpr by default and takes up no valuable RAM on small processors
  4. Is scoped and accessible by enum::value
  5. Works in case statements
  6. Provides type safety when used as a parameter and needs to be explicitly cast

Now if you define a class as an enum you can't create constexpr instances of the enum in the class declaration, because the class is not yet complete and it leads to a compile error. Also even if this worked you could not extend the value set of enums easily later in another file/sub project .

Now namespaces have no such problem but they don't provide type safety.

The answer is to first create a templated base class which allows enums of different base sizes so we don't waste what we don't use.

template <typename TYPE>
class EnumClass {
  private:
    TYPE value_;
  public:
    explicit constexpr EnumClass(TYPE value) :
        value_(value){
    }
    constexpr EnumClass() = default;
    ~EnumClass() = default;
    constexpr explicit EnumClass(const EnumClass &) = default;
    constexpr EnumClass &operator=(const EnumClass &) = default;

    constexpr operator TYPE() const {return    value_;}
    constexpr TYPE value() const {return value_;}

};

Then for each enum class we want to extend and emulate we create a namespace and a Type like this:

namespace EnumName {
   class Type :public Enum<uint8_t> {
     public:
        explicit constexpr Type(uint8_t value): Enum<uint8_t>(value){}
        constexpr Enum() = default;
   }
   constexpr auto Value1 = Type(1); 
   constexpr auto Value2 = Type(2); 
   constexpr auto Value3 = Type(3); 
}

Then later in your code if you have included the original EnumName you can do this:

   namespace EnumName {
       constexpr auto Value4 = Type(4U); 
       constexpr auto Value5 = Type(5U); 
       constexpr auto Value6 = Type(6U); 

       constexpr std::array<Type, 6U> Set = {Value1, Value2, Value3, Value4, Value5, Value6};
    }

now you can use the Enum like this:

#include <iostream>

void fn(EnumName::Type val){
    if( val != EnumName::Value1 ){
      std::cout << val;
    }
}

int main(){
  for( auto e :EnumName::Set){
    switch(e){
      case EnumName::Value1:  
        std::cout << "a";
        break;
      case EnumName::Value4:  
        std::cout << "b";
        break;
      default:
        fn(e);
    }
  }
}

So we have a case statement, enum comparisons, parameter type safety and its all extensible. Note the set is constexpr and wont end up using valuable RAM on a small micro (placement verified on Godbolt.org. :-). As a bonus we have the ability to iterate over a set of enum values.

A simple, but useful workaround for this c++ gap could be as follows:

#define ENUM_BASE_VALS A,B,C
enum Enum {ENUM_BASE_VALS};
enum EnumEx {ENUM_BASE_VALS, D,E,F};

I do this

enum OPC_t // frame Operation Codes
{
      OPC_CVSND = 0 // Send CV value
    , OPC_CVREQ = 1 // Request CV (only valid for master app)
    , OPC_COMND = 2 // Command
    , OPC_HRTBT = 3 // Heart Beat
};
enum rxStatus_t     // this extends OPC_t
{
      RX_CVSND = OPC_CVSND  // Send CV value
    , RX_CVREQ = OPC_CVREQ  // Request CV
    , RX_COMND = OPC_COMND  // Command
    , RX_HRTBT = OPC_HRTBT  // Heart Beat
    , RX_NONE       // No new Rx
    , RX_NEWCHIP        // new chip detected
};

One might try this :)


struct EnumType
{
        enum 
        {
            None = 0,
            Value_1 = 1,
            Value_2 = 2,
            Value_3 = 3
        };

        //For when using the EnumType as a variable type
        int Value { None };
};

struct EnumTypeEx : EnumType
{
        enum
        {
            ExValue_1 = 3,
            ExValue_2 = 4,
            
            //override the value of Value_3
            Value_3 = 3000
        };
};

Pros:

  • Class like extensibility
  • Can override base labels value

Cons:

  • Tedious to write
  • You have to explicitly set the value for each label(*)

(*) you can start the auto value increment from the last base value by just writing it explicitly eg. ExValue_Start = LastBaseValue.

Actually you can extend enums in a round about way.

The C++ standard defines the valid enum values to be all the valid values of the underlying type so the following is valid C++ (11+). Its not Undefined Behaviour, but it is very nasty - you have been warned.

#include <cstdint>

    enum Test1:unit8_t {
        Value1 =0,
        Value2 =1
    };

    constexpr auto Value3 = static_cast<Test1>(3);
    constexpr auto Value4 = static_cast<Test1>(4);
    constexpr auto Value5 = static_cast<Test1>(5);


Test1 fn(Test1 val){
  switch(val){
    case Value1:
    case Value2:
    case Value3:
    case Value4:
       return Value1;
    case Value5:
       return Value5; 
  } 
}

int main(){
  return static_cast<uint8_t>(fn(Value5));
} 

Note that most of the compilers don't consider the additional values as part of the set for generating warnings about missing enums values in switch statements.So clang and gcc will warn if Value2 is missing but will do nothing if Value4 is missing in the above switch statement.

My approach slightly differs from that chosen by Marco.

I have added a macro as the last value of the enum I want to extend. In a separate header, I have defined that macro with the enum entries I want to add.

// error.h
#ifndef __APP_DEFINED__
#define __APP_DEFINED__
#endif
typedef enum { a, b, c, __APP_DEFINED__ } Error;

To add more values, I have created

// app_error.h
#define __APP_DEFINED__ d, e, f,

Lastly, I have added -include app_error.h to the compiler flags.

I prefer this approach compared to placing an #include "app_error.h" directive within the enum, mainly because I need the freedom not to have the "app_error.h" file at all in some projects.

Related