My goal is to achieve best code / design practices:
I have functions that return 2 possible enums which can be a (state enum) or an (error enum).
in order to distinguish between the two enums, they have to be far values from each other and never overlap for example:
other modules has function are expecting the (error enum). and i do not want to make mapping function from state to error. so i have to contain (enum error) inside (enum state).
Enum eSomeState{
enum1Value = 0,
...
enum1Value99 = 99, // <<max
};
//defined in different module:
Enum eSomeError{
enum2Value = 100, // <<min
...
enum2Value99 = 199,
};
as if they overlapped, it will be impossible to know which one was returned from the called function (without extra flag).
So, earlier in my code i used to define two enums, (combining all error in error enum) into (state enum) .. like following
Version 1:
/* used by this module */
enum eSomeState{
eSomeState_A = 1,
eSomeState_B = 2,
eSomeState_C = 3,
eSomeState_D = 4,
/* do not reach 100 */
/* DO NOT GO BELOW 100 */ <------------- copied from other module's enum
/* every time i update eSomeErrorFromOtherModule, i have to come here and update this as well */
eSomeStateError_ErrA = 100,
eSomeStateError_ErrB = 101,
eSomeStateError_ErrC = 102,
};
/* used by this module and other module that cares about error handling */
/* defined in different scope */
enum eSomeErrorFromOtherModule{
eSomeError_none = 0,
/* DO NOT GO BELOW 100 */
eSomeError_ErrA = 100,
eSomeError_ErrB = 101,
eSomeError_ErrC = 102,
};
bool doSomthingA( enum eSomeState* const state){
Assert(*state == eSomeState_A);
//do stuff
if (success){
*state = eSomeState_B;
return true;
}else{
*state = err;
}
return false;
}
functions from other modules are expecting (Error Enum) type so i used to cast the (State Enum) to (Error Enum)
after some time, i noticed that its hard to keep copying the (Error Enum) to (State Enum) and keep them identical everytime i need to modify one of them.
so i moved to Version 2:
/* used by this module */
enum eSomeState{
eSomeState_A = 1,
eSomeState_B = 2,
eSomeState_C = 3,
eSomeState_D = 4,
/* do not reach 100 */
}
union uSomeState{
enum eSomeState state;
enum eSomeErrorFromOtherModule err;
}
/* functions updated to */
bool doSomthingA( union eSomeState* const state){
Assert(state->state == eSomeState_A);
//do stuff
if (success){
state->state = eSomeState_B;
return true;
}else{
state->err = err;
}
return false;
}
version 2 [i didnt test it as i barely used union but i expect it to work] have reduced my work to go copy enum from another module to this module .. but i still have to make sure that (state enum) doesnt overlap (error enum) ..
I like to think of enums as a set (list) of constants. If i have to store a state, then i introduce a new type, e.g. bitset_t or state_t.
Example:
typedef int state_t;
enum someStates {};
enum someErrorStates {};
bool doSomething(state_t state) {}
Because sometimes it is necessary to 'combine' two states together (e.g. by the operator '|'), which is not in the set of either enums.
Just an opinion.
Supplement:
Basically, an enum is an int (unsigned?) and you can store upto INT_MAX values in one set. But if you want a 'bitset', then you are restricted to 32 (if int are 32 bits) distinct values. If you want to have more, then you have to implement a bitset, an array of words.
Example:
#define word_size sizeof(unsigned int)
#define word_width (word_size * CHAR_BIT) // defined in limits.h
#define word_index(Pos) ((Pos) / word_width)
#define bit_index(Pos) ((Pos) % word_width)
unsigned int bitset[N];
bool isSet(unsigned int bs[N], unsigned int pos)
{
return ((bs[word_index(pos)] >> bit_index(pos)) & 1);
}
void setBit(unsigned int bs[N], unsigned int pos)
{
bs[word_index(pos)] |= (1 << bit_index(pos));
}
void clearBit(unsigned int bs[N], unsigned int pos)
{
bs[word_index(pos)] &= ~(1 << bit_index(pos));
}
void toggleBit(unsigned int bs[N], unsigned int pos)
{
bs[word_index(pos)] ^= (1 << bit_index(pos));
}
And then you could define your enums like that:
enum someState {
state1 = 1,
state2 = 2
//...
};
setBit(bitset, state2);
isSet(bitset, state2);
// ... and so on
Basically, the enum constants will describe the (bit) position in the bitset.