-
Notifications
You must be signed in to change notification settings - Fork 393
enum or enum class: Either Way, Use Them
Enumerations, sometimes called enumerated sets, are an important part of any programming language. Even Fortran had enumerations starting in 2003, but having started with older versions of Fortran EnergyPlus did not use them. Enumerations were part of the original C language specifications using the keyword enum
.
enum TimeStepType {
System, // implied value of first element is 0, can be overridden with = <value>
HVAC, // implied value of element is value of previous element + 1, can be overridden with = <value>
Zone,
Plant
};
enum TimeStepType ts = HVAC;
A C-style enum
is essentially a subtype of int
. Because of this, you can also assign enum
values to int
variables and compare enum
variables to int
values directly. This is considered "type unsafe" (not by me personally, by the internet) and is therefore frowned upon. Note, you cannot assign an int
value to an enum
because subtyping does not work in that direction, the right-hand side always has to be either the same type or a subtype of the left-hand side in an assignment. If this were the case, then that would truly be type unsafe, but it is not.
int tsint = HVAC; // this is allowed but is considered type unsafe
bool isTSZone = (ts == 3); // this is also allowed and is also considered type unsafe
enum TimeStepType ts = 3; // this is an error
Before C++-11 there was also the possibility of name clashing between different enum
s or enum
's and variables because enum
's were considered part of the enclosing name scope. Starting in C++-11, enum
's are still considered part of the enclosing scope, but they can also be explicitly scope-resolved using the ::
operator to avoid name clashes. This is good!
enum TimeStepType ts = TimeStepType::HVAC;
The C++-11 standard introduced the enum class
construct which is both its own name scope (i.e., explicit scope resolution is required) and not an implicit subtype of int
.
enum class TimeStepType {
System, // implied value of first element is 0, can be overridden with = <value>
HVAC, // implied value of element is value of previous element + 1, can be overridden with = <value>
Zone,
Plant
};
TimeStampType ts = HVAC; // this is an error, HVAC scope must be resolved
int tsint = TimeStepType::HVAC; // this is also an error, TimeStepType is not a subtype of int
bool isTSZone = (ts == 3); // this is also an error for the same reason
TimeStepType ts = 3; // this was an error before and is still an error
Note, putting : int
after an enum class
declaration:
enum class TimeStepType : int {
};
Does not make it into a subtype of int
, it only specifies that the size of the variable has to be the size of int
. Since :
is used to indicated subtyping in the class
construct, this is confusing. Anyway, the same internet considers enum class
to be be preferable to enum
, so there you have it.
enum
's of any kind are worlds of fun! Let's see some examples.
enum class
es are explicitly not subtypes of int
, but they are actually implemented as int
's and it is often useful to treat them that way. You can treat a enum class
as an int
using static_cast<int>()
. static_cast<>()
does not generate a function call or any other runtime code, it is a way of telling the compiler "I know what I am doing! I know that treating a enum class
as an int
is unsafe in the general case, but in this specific case it is safe because I know something about the range of integers that will be generated."
A common reason to use enum class
es as int
s is to use them as indices in an array. A common example: mapping enum class
es to strings and back. (If interested, here are short tutorials on constexpr
and std::string_view
)
enum class TimeStepType {
Invalid = -1, // this is the only "good programming" use of a negative enum, i.e., error
System,
HVAC,
Zone,
Plant,
NUM // good hygiene to name the last member of the enum NUM so that it can be used as the number of elements in the enum
};
constexpr std::array<std::string_view, static_cast<int>(TimeStepType::NUM)> // NUM is used to size the array, must be cast to int
TimeStepTypeNamesUC = {
"SYSTEM",
"HVAC",
"ZONE",
"PLANT"};
// Print out all TimeStepTypes, note use of NUM and static_cast<int>
for (int i = 0; i < static_cast<int>(TimeStepType::NUM); ++i)
std::cout << TimeStepTypeNamesUC[i] << std::endl;
// A function that converts TimeStepType name to the enumeration.
TimeStepType
getTimeStepType(std::string_view name)
{
for (int i = 0; i < static_cast<int>(TimeStepType::NUM); ++i)
if (TimeStepTypeNamesUC[i] == name)
return static_cast<TimeStepType>(i);
return TimeStepType::Invalid;
}
// A general function that converts any name to the corresponding enumeration.
int
getEnumerationValue(const gsl::span<std::string_view> list, std::string_view name)
{
for (int i = 0; i < list.size(); ++i)
if (list[i] == name)
return i;
return -1;
}
getEnumerationValue
is an EnergyPlus function and the combination of this function and constexpr std::array<std::string_view, NUM>
is the preferred way of converting enumeration names to values. Please do not use a std::map
to do this. A std::map
makes sense for some things , specially large dynamic data sets, but not for this. A std::map
is a heap-allocated red-black binary tree and cannot be made constexpr
. EnergyPlus may spend more time setting up the std::map
than actually doing lookups in it. (See this page on different C++ standard library containers and when to use which)
An important aspect of using enum
's is learning to see them where they don't already exist. Check out this error/diagnostic-tracking/printing example from the EnergyRecovery
module. This is actually a subset of the machinery, but you will get the idea.
// regen inlet relative humidity for temperature equation
bool PrintRegenInRelHumTempMess; // - flag to print regen in RH error message for temp eq
int RegenInRelHumTempErrIndex; // - index to recurring error struc for regen outlet hum rat
int RegenInRelHumTempErrorCount; // - counter if regen outlet temp limits are exceeded
std::string RegenInRelHumTempBuffer1; // - buffer for RegenOutHumRat warn mess on following timstep
std::string RegenInRelHumTempBuffer2; // - buffer for RegenOutHumRat warn mess on following timstep
std::string RegenInRelHumTempBuffer3; // - buffer for RegenOutHumRat warn mess on following timstep
Real64 RegenInRelHumTempLast; // - last value of regen outlet humidity ratio
// process inlet relative humidity for temperature equation
bool PrintProcInRelHumTempMess; // - flag to print regen in RH error message for temp eq
int ProcInRelHumTempErrIndex; // - index to recurring error struc for regen outlet hum rat
int ProcInRelHumTempErrorCount; // - counter if regen outlet temp limits are exceeded
std::string ProcInRelHumTempBuffer1; // - buffer for RegenOutHumRat warn mess on following timstep
std::string ProcInRelHumTempBuffer2; // - buffer for RegenOutHumRat warn mess on following timstep
std::string ProcInRelHumTempBuffer3; // - buffer for RegenOutHumRat warn mess on following timstep
Real64 ProcInRelHumTempLast; // - last value of regen outlet humidity ratio
// used when regen outlet humrat is below regen inlet humrat, verify coefficients warning issued
bool PrintRegenOutHumRatFailedMess; // - flag for regen outlet hum rat error message
int RegenOutHumRatFailedErrIndex; // - index to recurring error struc for regen outlet hum rat
int RegenOutHumRatFailedErrorCount; // - counter if regen outlet temp limits are exceeded
std::string RegenOutHumRatFailedBuffer1; // - buffer for RegenOutHumRat warn mess on following timstep
std::string RegenOutHumRatFailedBuffer2; // - buffer for RegenOutHumRat warn mess on following timstep
std::string RegenOutHumRatFailedBuffer3; // - buffer for RegenOutHumRat warn mess on following timstep
Real64 RegenOutHumRatFailedLast; // - last value of regen outlet humidity ratio
// used when regen and process mass flow rates are not equal to within 2%
bool PrintImbalancedMassFlowMess; // - flag for imbalanced regen and process mass flow rate
int ImbalancedFlowErrIndex; // - index to recurring error struc for imbalanced flow
int ImbalancedMassFlowErrorCount; // - counter for imbalanced regen and process mass flow rate
std::string ImbalancedMassFlowBuffer1; // - buffer for imbalanced regen and process mass flow rate
std::string ImbalancedMassFlowBuffer2; // - buffer for imbalanced regen and process mass flow rate
std::string ImbalancedMassFlowBuffer3; // - buffer for imbalanced regen and process mass flow rate
Real64 ImbalancedMassFlowLast; // - last value of heat exchanger mass flow rate imbalance ```
Here is the code file:
// print error when regeneration inlet relative humidity is outside model boundaries
if (state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex).PrintRegenInRelHumTempMess) {
++state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex).RegenInRelHumTempErrorCount;
if (state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex).RegenInRelHumTempErrorCount < 2) {
ShowWarningError(state,
state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex).RegenInRelHumTempBuffer1);
ShowContinueError(state,
state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex). .RegenInRelHumTempBuffer2);
ShowContinueError(state,
state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex).RegenInRelHumTempBuffer3);
ShowContinueError(state,
"...Using regeneration inlet air relative humidities that are outside the regeneration outlet temperature "
"equation model boundaries may adversely affect desiccant model performance. Verify correct model"
"coefficients.");
}
}
Here's a slightly more readable version of the code file that uses a local reference variable to shorten the chain of indexed array lookups:
// print error when regeneration inlet relative humidity is outside model boundaries
auto &balDesDehumPerfData(state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex));
if (balDesDehumPerfData.PrintRegenInRelHumTempMess) {
++balDesDehumPerfData.RegenInRelHumTempErrorCount;
if (balDesDehumPerfData.RegenInRelHumTempErrorCount < 2) {
ShowWarningError(state, balDesDehumPerfData.RegenInRelHumTempBuffer1);
ShowContinueError(state, balDesDehumPerfData.RegenInRelHumTempBuffer2);
ShowContinueError(state, balDesDehumPerfData.RegenInRelHumTempBuffer3);
ShowContinueError(state,
"...Using regeneration inlet air relative humidities that are outside the regeneration outlet temperature "
"equation model boundaries may adversely affect desiccant model performance. Verify correct model"
"coefficients.");
}
}
Now, that we have a more readable code, consider this enum
-based implementation instead:
enum class HeatRecoveryError_t : int {
Invalid = -1,
RegenInRelHumTemp,
ProcInRelHumTemp,
RegenOutHumRatFailed,
ImbalancedMassFlow,
NUM
};
struct HeatRecoveryError_c {
bool PrintMessage;
int ErrorIndex;
int ErrorCount;
std::string buffer1, buffer2, buffer3;
Real64 lastValue;
};
std::array<HeatRecoveryError_c, static_cast<int>(HeatRecoveryError_c::NUM)> errors;
And here is the code file:
// print error when regeneration inlet relative humidity is outside model boundaries
auto &balDesDehumPerfData(state.dataHeatRecovery->BalDesDehumPerfData(state.dataHeatRecovery->ExchCond(ExchNum).PerfDataIndex));
auto &balDesDehumPerfDataError(balDesDehumPerfData.errors[static_cast<int>(HeatRecoveryError_t::RegenInRelHumTemp)]);
if (balDesDehumPerfDataError.PrintMess) {
++balDesDehumPerfDataError.ErrorCount;
if (balDesDehumPerfDataError.ErrorCount < 2) {
ShowWarningError(state, balDesDehumPerfDataError.buffer1);
ShowContinueError(state, balDesDehumPerfDataError.buffer2);
ShowContinueError(state, balDesDehumPerfDataError.buffer3);
ShowContinueError(state,
"...Using regeneration inlet air relative humidities that are outside the regeneration outlet temperature "
"equation model boundaries may adversely affect desiccant model performance. Verify correct model"
"coefficients.");
}
}
This is now a very generic piece of code that can be encapsulated in a function or loop rather than copy-pasted fifteen times.
Another good use of enum
's is in switch
/case
statements. The switch
/case
construct is a good (and fast) replacement for if-else-if logic
, especially when there is not a single dominant case, i.e., one case that occurs at least 80% of the time. Instead of:
if (shading == WinShadingType::IntShade) {
...
} else if (shading == WinShadingType::ExtShade || shading == WinShadingType::ExtScreen) {
...
} else if (shading == WinShadingType::ExtScreen) {
...
} else if (
You can use:
switch (shading) {
case WinShadingType::IntShade: {
...
}
break; // use break between cases otherwise the code will "drop" to the next case.
case WinShadingType::ExtShade:
case WinShadingType::ExtScreen: { // putting two case statements together like this is the same as putting an || in the conditional
...
}
break;
...
default: // the compiler may complain if you don't put in a default statement
assert(false); // use an assert if you do not plan on ever getting here
}
The switch
statement uses an array of code addresses (called a jump table or a code pointer table) indexed by the enum
itself to jump to any case in only three instructions, as opposed to having to test the cases sequentially. Having compact enum
's that start at 0 is important for keeping the jump table to a reasonable size. Here are the pseudo-instructions:
ADD JUMP-TABLE, R1 -> R2 // assume shading variable is in register R1, the address of JUMP-TABLE is a compile time constant and can be hard-coded
LOAD R2 -> R2
JUMP-INDIRECT R2
The switch
statement is fast and also makes the code look clean, but it has some limitations. Specifically, the tests can only be on a single int
/enum
/enum class
variable and they can only be equality tests. This restriction is what enables the use of the int
/enum
/enum class
as an index in the jump table. Incidentally, when you use enum class
in a switch
statement the compiler implicitly applies static_cast<int>
to them. So much for using enum class
as int
being "type unsafe".