19

Is it possible to have enum of enums in c++. I have to have something like:

Error types:

  • Type1
  • Type2
  • Type3

Type1:

  • cause1
  • cause2

Type2:

  • cause3
  • cause4

Type3:

  • cause5
  • cause6

Each of these are integer values. They are supposed to be used in a communication protocol stack. At the receiving side the receiver has to decode the type and cause of the error from the values received. If enums can't be used what would be the best way to do it?

3
  • 11
    Sorry, but the answer is no -- you'll need to find a different way to do things. One typical one is to encode the type in some number of upper bits in the number, and the cause in some number of lower bits (e.g., 16 bit value, 8 bits for each). Commented Mar 15, 2013 at 8:32
  • It is not possible to have enum of enums, but you could represent your data by having the type and cause separately either as part of a struct or allocating certain bits for each of the fields. Commented Mar 15, 2013 at 8:35
  • 1
    even if you could have an enum of enum, since there the program is run by two different machines, the value of the same sub-enum (e.g. Type1Cause1) may be instantiated differently. Isn't it dangerous ? Commented Mar 15, 2013 at 8:38

4 Answers 4

9

I'm not even sure what an enum of enums would mean. But the usual way of handling this is either to define ranges in a single enum:

enum Errors { type1 = 0x000, cause1, cause2, type2 = 0x100, cause3, cause4, ... causeMask = 0xFF, typeMask = 0xFF00 }; 

Or to simply define separate enums, in separate words, and use unsigned (or unsigned short, or what ever) and a bit of casting for the different causes.

Whatever the solution adopted, I would encapsulate it in a class, so that client code only has to deal with errorType() and errorCause(); errorCause() could even be a template on the error type value. (But somewhere, you'll need explicit specializations for each type value, because the compiler will not otherwise know how to map value to cause type.)

Sign up to request clarification or add additional context in comments.

1 Comment

Right, somehow embedding the type in the cause is the only way to make sure you don't mix the wrong types and causes. And this is the simplest way of doing it.
4

As Jerry said, it's not possible directly. One way to solve this is to have two enums. One for the category, and one for the sub-category.

However, as georgesl said, it probably can be dangerous to do this in a protocol. You definitely should explicitely define the enum values:

struct Error { enum Type { UNKNOWNTYPE = 0, TYPE1 = 1, TYPE2 = 2, TYPE3 = 3 }; enum Subtype { UNKNOWNSUBTYPE = 0, // subtype for error type 1 CAUSE1 = 1001, CAUSE2 = 1002, CAUSE3 = 1003, // subtype for error type 2 CAUSE4 = 2001, CAUSE5 = 2002 }; Type type; Subtype subtype; }; int main() { Error error; error.type = Error::TYPE1; error.subtype = Error::CAUSE1; } 

Make sure to choose the numbers wisely for future extensions.

Update: made the example actually work.

Alternative, more typesafe solution:

struct ErrorType { enum type { UNKNOWNTYPE = 0, TYPE1 = 1, TYPE2 = 2, TYPE3 = 3 }; }; struct ErrorSubtype { enum type { UNKNOWNSUBTYPE = 0, // subtype for error type 1 CAUSE1 = 1001, CAUSE2 = 1002, CAUSE3 = 1003, // subtype for error type 2 CAUSE4 = 2001, CAUSE5 = 2002 }; }; struct Error { ErrorType::type type; ErrorSubtype::type subtype; }; int main() { Error error; error.type = ErrorType::TYPE1; error.subtype = ErrorSubtype::CAUSE1; } 

7 Comments

In this case, do I need the enum type, as it is already getting encoded in subtype?
But the problem with this design is that I can join an error type with a wrong subtype, ie. i can couple TYPE1 with CAUSE4, right?
Yes, that is the case. If you want to decouple even that, you can make three structs: Error, ErrorType and ErrorSubtype. And then make Error include the other two.
I have added a more typesafe solution.
One could use the evil macros for creating combined enum values. This would prevent using the wrong subtype. I could give an answer if preprocessor macros are an option...
|
3

I would not recommend doing this. Prefer to use an explicit error type, containing information about errors (You could add strings etc.). Also this is not very type safe. See also James answer.

But anyway here is the evil macro version:

#define DECL_ERROR_TYPE(errorType, value) , errorType = value << 16 #define DECL_ERROR(errorType, cause, value) , errorType##_##cause = (errorType + value) #define GET_ERROR_TYPE(error) (error & 0xFFFF0000) enum Error { NoError = 0 DECL_ERROR_TYPE(Type1, 1) DECL_ERROR(Type1, Cause1, 1) DECL_ERROR(Type1, Cause2, 2) DECL_ERROR_TYPE(Type2, 2) DECL_ERROR(Type2, Cause1, 1) DECL_ERROR_TYPE(Type3, 3) DECL_ERROR(Type3, Cause1, 1) DECL_ERROR(Type3, Cause2, 2) }; 

This allows you to use it like this:

Error err1 = Type1_Cause1; if(Type1 == GET_ERROR_TYPE(err1)) return 0; // Works 

Comments

0

I just could not stand using enums. So i have another answer using explicit types. It is not complete but shows the right direction, and contains the possible extension of adding descriptions etc.

Here´s the code for the declaration:

struct Error { public: struct ErrorType { int _code; ErrorType(int code) : _code(code << 16) {} }; private: friend struct Errors; ErrorType _type; int _code; Error(ErrorType type, int causeCode) : _type(type), _code(causeCode) { } static std::map<int, Error> _errors; public: Error() : _type(-1), _code(-1) {} static Error FromCode(int code) { return _errors[code]; } bool IsOfType(const ErrorType& type ) { return _type._code == type._code; } operator int() { return _code | _type._code; } bool operator == (Error const& other) const { return _code == other._code && _type._code == other._type._code; } bool operator != (Error const& other) const { return _code != other._code || _type._code != other._type._code;; } }; std::map<int, Error> Error::_errors; struct Errors { #define BEGIN_TYPE(type, code) struct type : Error::ErrorType { type() : ErrorType(code) {} typedef Errors::##type CurrentType; #define CAUSE(cause, code) struct cause : Error { cause() : Error(CurrentType(),code) { Error::_errors[*this] = *this; } }; #define END_TYPE() }; // first type is coded manually to show what the macros do... struct Type1 : Error::ErrorType { Type1() : ErrorType(1) { } typedef Errors::Type1 CurrentType; struct Cause1 : Error { Cause1() : Error(CurrentType(),1) { Error::_errors[*this] = *this; } }; struct Cause2 : Error { Cause2() : Error(CurrentType(),2) { Error::_errors[*this] = *this; } }; }; BEGIN_TYPE(Type2, 2) CAUSE(Cause1, 1) CAUSE(Cause2, 2) END_TYPE() }; 

And here are some example usages:

int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Error err = Errors::Type1::Cause1(); Q_ASSERT( err.IsOfType(Errors::Type1()) ); Q_ASSERT( Errors::Type1::Cause1() == Errors::Type1::Cause1() ); Q_ASSERT( Errors::Type1::Cause1() != Errors::Type2::Cause1() ); int code = err; qDebug() << code; Q_ASSERT( Error::FromCode(code) == Errors::Type1::Cause1() ); Q_ASSERT( Error::FromCode(code) != Errors::Type2::Cause1() ); Q_ASSERT( Error::FromCode(code).IsOfType(Errors::Type1()) ); return a.exec(); } 

It´s not a perfect solution but shows how this can be handled in a more explicit manner. There are many improvements one can do...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.