Reputation: 1599
Is it possible to have enum of enums in c++. I have to have something like:
Error types:
Type1:
Type2:
Type3:
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?
Upvotes: 19
Views: 11942
Reputation: 2489
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...
Upvotes: 0
Reputation: 2489
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
Upvotes: 3
Reputation: 153909
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.)
Upvotes: 9
Reputation: 2674
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;
}
Upvotes: 4