In this case, I would say the ideal answer is that it depends on how the enums are consumed, but that in most circumstances it's probably best to define all enums separately, but if any of them are already coupled by design, you should provide a means of introducing said coupled enums collectively. In effect, you have a coupling tolerance up to the amount of intentional coupling already present, but no more.
Considering this, the most flexible solution is likely to define each enum in a separate file, but provide coupled packages when it's reasonable to do so (as determined by the intended usage of the enums involved).
Defining all of your enumerations in the same file couples them together, and by extension causes any code that depends on one or more enums to depend on all enums, regardless of whether the code actually uses any other enums.
#include "enumList.h"
// Draw map texture. Requires map_t.
// Not responsible for rendering entities, so doesn't require other enums.
// Introduces two unnecessary couplings.
void renderMap(map_t, mapIndex);
renderMap()
would much rather only know about map_t
, because otherwise any changes to the others will affect it even though it doesn't actually interact with the others.
#include "mapEnum.h" // Theoretical file defining map_t.
void renderMap(map_t, mapIndex);
However, in the case where components are already coupled together, providing multiple enums in a single package can easily provide additional clarity and simplicity, provided that there's a clear logical reason for the enums to be coupled, that usage of those enums is also coupled, and that providing them doesn't also introduce any additional couplings.
#include "entityEnum.h" // Theoretical file defining entity_t.
#include "materialsEnum.h" // Theoretical file defining materials_t.
// Can entity break the specified material?
bool canBreakMaterial(entity_t, materials_t);
In this case, there's no direct, logical connection between entity type and material type (assuming that the entities aren't made of one of the defined materials). If, however, we had a case where, e.g., one enum is explicitly dependent on the other, then it makes sense to provide a single package containing all coupled enums (as well as any other coupled components), so that the coupling can be isolated to that package as much as is reasonably possible.
// File: "actionEnums.h"
enum action_t { ATTACK, DEFEND, SKILL, ITEM }; // Action type.
enum skill_t { DAMAGE, HEAL, BUFF, DEBUFF, INFLICT, NONE }; // Skill subtype.
// -----
#include "actionTypes.h" // Provides action_t & skill_t from "actionEnums.h", and class Action (which couples them).
#include "entityEnum.h" // Theoretical file defining entity_t.
// Assume ActFlags is or acts as a table of flags indicating what is and isn't allowable, based on entity_t and Action.
ImplementationDetail ActFlags;
// Indicate whether a given type of entity can perform the specified action type.
// Assume class Action provides members type() and subtype(), corresponding to action_t and skill_t respectively.
// Is only slightly aware of the coupling; knows type() and subtype() are coupled, but not how or why they're coupled.
bool canAct(entity_t e, const Action& act) {
return ActFlags[e][act.type()][act.subtype()];
}
But alas... even when two enums are intrinsically coupled together, even if it's something as strong as "second enum provides subcategories for first enum", there may still come a time where only one of the enums is necessary.
#include "actionEnums.h"
// Indicates whether a skill can be used from the menu screen, based on the skill's type.
// Isn't concerned with other action types, thus doesn't need to be coupled to them.
bool skillUsableOnMenu(skill_t);
// -----
// Or...
// -----
#include "actionEnums.h"
#include "gameModeEnum.h" // Defines enum gameMode_t, which includes MENU, CUTSCENE, FIELD, and BATTLE.
// Used to grey out blocked actions types, and render them unselectable.
// All actions are blocked in cutscene, or allowed in battle/on field.
// Skill and item usage is allowed in menu. Individual skills will be checked on attempted use.
// Isn't concerned with specific types of skills, only with broad categories.
bool actionBlockedByGameMode(gameMode_t mode, action_t act) {
if (mode == CUTSCENE) { return true; }
if (mode == MENU) { return (act == SKILL || act == ITEM); }
//assert(mode == BATTLE || mode == FIELD);
return false;
}
Therefore, since we know both that there can always be situations where defining multiple enumerations in a single file can add unnecessary coupling, and that providing coupled enums in a single package can clarify the intended usage and allow us to isolate the actual coupling code itself as much as possible, the ideal solution is to define each enumeration separately, and provide joint packages for any enums that are intended to frequently be used together. The only enums defined in the same file will be ones that are intrinsically linked together, such that usage of one necessitates usage of the other as well.
// File: "materialsEnum.h"
enum materials_t { WOOD, STONE, ETC };
// -----
// File: "entityEnum.h"
enum entity_t { PLAYER, MONSTER };
// -----
// File: "mapEnum.h"
enum map_t { 2D, 3D };
// -----
// File: "actionTypesEnum.h"
enum action_t { ATTACK, DEFEND, SKILL, ITEM };
// -----
// File: "skillTypesEnum.h"
enum skill_t { DAMAGE, HEAL, BUFF, DEBUFF, INFLICT, NONE };
// -----
// File: "actionEnums.h"
#include "actionTypesEnum.h"
#include "skillTypesEnum.h"
materials_t
files that don't deal in materials will have to be rebuilt.enumList.h
serve as a collection of#include
s for those files. This allows files that only need one enum to get it directly, while providing a singular package for anything that really does want all of them.