4

I was working on creating a multilevel enum for a Library.

  • I would like something (not necessarily exactly) like this:
Book::Category::PROGRAMMING::C_PLUS_PLUS

where Book is a struct and Category is (currently) an enum class.
Basically, I would like to nest enums.

I know that I can just do:

Book::Category::PROGRAMMING_C_PLUS_PLUS

But not only will this create a long list of values in Category, but will make maintaining it a nightmare (I tried to do this with 30, and it was already a headache).

I want to divide and conquer this problem.

Edit 1: The reason I didn't want it to be a std::string is that it's value can be anything and everything. I wanted to constrain the domain of Category.

And no, PROGRAMMING is not a major part of my Library. Let me try with classes and inheritance.

Basically, the reason I wanted enums was to have a fixed set of valid constants.


References:

Jaideep Shekhar
  • 808
  • 2
  • 7
  • 21
  • 1
    By forcing them to be fixed and constant, you're forcing yourself into the "headache" situation no matter what. You're forcing yourself to maintain a constant list of valid enums that *may change* at some point, at which point you will have to update your list of enums and **recompile** your code. A malleable solution here would be to allow them to be strings, and read in a .txt file at startup or something, and have that be your list of acceptable values that you can check against if you need to verify. – alteredinstance Dec 18 '19 at 15:39
  • 1
    @alteredinstance Another very valid point! I think this should work. The program should have code, not a database. That belongs in a separate dedicated file. Do add to the answer! +1 – Jaideep Shekhar Dec 18 '19 at 15:43
  • 1
    Glad I was able to be helpful! Let me know how this works for you, hopefully it's not too much of a detour to implement. I think "The program should have code, not a database" is a great way of understanding the solution. – alteredinstance Dec 18 '19 at 15:51

3 Answers3

2

You cannot nest the enums, however, you can do something like this:

struct Book {
    struct Category {
        enum class PROGRAMMING {
            // ...
        };
        enum class COOKING {
            // ...
        }; 
    };
};

Similar to using a namespace like in the linked question, but since it is inside a class and you can't do that, you can just use another struct.

ChrisMM
  • 8,448
  • 13
  • 29
  • 48
  • Okayyy. This seems neat. So, basically make all n-1 levels a `struct` and the last level, a real `enum class`. Will still have to be careful to manage it properly. – Jaideep Shekhar Dec 18 '19 at 15:10
  • If you can't use namespaces for the upper levels, then yes. – ChrisMM Dec 18 '19 at 15:11
  • Okay, quick question. What if I have to store a category, that could be `PROGRAMMING` related, or `COOKING` related? – Jaideep Shekhar Dec 18 '19 at 15:17
  • Then you'd have a problem ;) You can put the same name in each `enum`, but they would be completely separate... – ChrisMM Dec 18 '19 at 15:20
1

You can do this, but it would be a bad design.

One common code smell is using an enum for what should clearly be a class - the case here being that you want a C_PLUS_PLUS genre to be a derivation of the PROGRAMMING category.

However, enums are best used to describe possible states, and are incapable of inheritance by design, as you noted in one of your references. In my opinion, your best course of action would be to make two separate member variables in Category that can hold values such as PROGRAMMING and C_PLUS_PLUS, and do away with their status as enums.

Think: what would best encapsulate an open-ended field such as a book genre/subgenre? Are you planning to program every single possibility with an enum? Or would just using two strings, (such as genre and subgenre) better suit an open-ended field such as that?

Or, perhaps, if you are specifically working with PROGRAMMING books as a major piece of your design, turn PROGRAMMING into a class, and make C_PLUS_PLUS part of an enum within that class. But don't have them both be enums - that's just asking for issues down the road, and doesn't solve your issues with maintenance that you already described.

EDIT: Adding from a comment I made -

By forcing them to be fixed and constant, you're forcing yourself into the "headache" situation no matter what. You're forcing yourself to maintain a constant list of valid enums that may change at some point, at which point you will have to update your list of enums and recompile your code. A malleable solution here would be to allow them to be strings, and read in a .txt file at startup or something, and have that be your list of acceptable values that you can check against if you need to verify.

alteredinstance
  • 587
  • 3
  • 17
1

On the request of an answerer, I am posting my implementation for future reference.
This is the Book:

struct Book
{
    // A Map of categories and its list of sub-categories
    static std::map< std::string, std::vector< std::string > > Categories;

    // default parameter for unit testing, 
    // thanks to https://stackoverflow.com/questions/40297782/c-unit-testing-check-output-is-correct/59374648#59374648
    static void initBookCategories(std::string file_name = "categories.txt")
    {
        // Open the file in read mode
        std::ifstream allowed_categories(file_name);

        // Check if file open is unsuccessful
        if(!allowed_categories)
        {
            // Signal error and exit
            std::cerr << "\n\nFile Not Found!\nError Initialising DB! Exiting...";
            return;
        }

        // Read the category_name
        std::string category_name;
        // Until there are categories left
        while(std::getline(allowed_categories, category_name))
        {
            // Read the whole sub-category list
            std::string subcategory_names;
            // Untill end of line
            std::getline(allowed_categories, subcategory_names);
            // Convert to std::stringstream for parsing
            std::istringstream parse_subcategories(subcategory_names);
            // Skip the empty line
            allowed_categories.ignore();

            // Now read the sub-categories
            // Need a std::vector to store the list of subcategory names
            std::vector< std::string > subcategory_list;
            // Reusing subcategory_names for each subcategory_name
            while(parse_subcategories >> subcategory_names)
            {
                // Add to the list of sub-categories
                subcategory_list.push_back(subcategory_names);
            }

            // Add this newly read category with its list of subcategories our map
            Categories.insert(std::pair< std::string, std::vector< std::string > >(category_name, subcategory_list));
        }
    }
    static void getCategoryList()
    {
        // For each category
        for(std::pair< const std::string, std::vector< std::string > >& category : Categories)
        {
            // List the sub-categories
            int index = 0;
            // Print the category name
            std::cout << " " << category.first << ":\n";
            // Loop over the sub-categories
            for(std::string& subcategory_name : category.second)
            {
                // Printing each sub-category in a line
                std::cout << "   " << ++index << ") " << subcategory_name << "\n";
            }
        }
    }
};
std::map< std::string, std::vector< std::string > > Book::Categories;

And our main function using this:

int main()
{
    Book::initBookCategories();
    Book::getCategoryList();
    return 0;
}

Last, but not least, the format of categories.txt:

PROGRAMMING
C_PLUS_PLUS C PYTHON JAVA SQL

MATHEMATICS
CALCULUS ALGEBRA DISCRETE

BIOLOGY
ZOOLOGY BOTANY MICROBIOLOGY BIOTECHNOLOGY

CHEMISTRY
ORGANIC INORGANIC PHYSICAL

SOCIAL_STUDIES
POLITICAL_SCIENCE GEOGRAPHY HISTORY CIVIL_SCIENCE

BUSINESS_ADMINISTRATION
ACCOUNTS MANAGEMENT STATISTICS ECONOMICS

SCIENCE_FICTION
SPACE ALIENS FUTURE WAR ADVENTURE

LITERATURE
ENGLISH HINDI COMEDY NOVEL

PHYSICS
MECHANICS FLUIDS NUCLEAR THERMODYNAMICS

ARTS_AND_HUMANITIES
PHILOSOPHY SOCIOLOGY PSYCHOLOGY ANTHROPOLOGY

Thanks!

Jaideep Shekhar
  • 808
  • 2
  • 7
  • 21