c++initializationunordered-setenum-class

How to iterate over enumerators of an enum class?


Is there a way to initialize a container (e.g. std::unordered_set<char>) with the enumerators of an enum class?

I have this class:

#include <iostream>
#include <unordered_set>


class Foo
{
public:

    inline static const std::unordered_set<char> chars_for_drawing { '/', '\\', '|', '-' };
};


int main( )
{
    for ( const char ch : Foo::chars_for_drawing )
    {
        std::cout << ch << ' ';
    }
}

But I want the chars_for_drawing set to be initialized with the enumerators:

#include <iostream>
#include <unordered_set>


class Foo
{
public:
    enum class AllowedChars : char
    {
        ForwardSlash = '/',
        BackSlash = '\\',
        VerticalSlash = '|',
        Dash = '-'
    };

    // inline static const std::unordered_set<char> chars_for_drawing { '/', '\\', '|', '-' }; // not like this

    inline static const std::unordered_set<char> chars_for_drawing {
                                                                     static_cast<char>( AllowedChars::ForwardSlash ),
                                                                     static_cast<char>( AllowedChars::BackSlash ),
                                                                     static_cast<char>( AllowedChars::VerticalSlash ),
                                                                     static_cast<char>( AllowedChars::Dash )
                                                                    };
};


int main( )
{
    for ( const char ch : Foo::chars_for_drawing )
    {
        std::cout << ch << ' ';
    }
}

As can be seen, the second approach is a bit messy. Is there way to iterate over the enumerators and assign them to the unordered_set? Maybe by using a lambda?


Solution

  • No there is no straightforward way. Something one often forgets: The range of the enums values is determined by its underlying type. The enumerators are just some named constants. Your enum:

    enum class AllowedChars : char
    {
        ForwardSlash = '/',
        BackSlash = '\\',
        VerticalSlash = '|',
        Dash = '-'
    };
    

    helps for iterating as much as a

    struct {
        char value;
        static const char ForwardSlash = '/';
        static const char BackSlash = '\\';
        static const char VerticalSlash = '|';
        static const char Dash = '-';
    };
    

    does: Not at all.

    Things are different when the enumerators have consecutive values and a hack that is used sometimes is to use a special enumerator to denote the "size":

    enum class AllowedChars : char
    {
        ForwardSlash,
        BackSlash,
        VerticalSlash,
        Dash,
        SIZE
    };
    
    int main() {
        for (int i=0;i< static_cast<int>(AllowedChars::SIZE); ++i){
            std::cout << i;
        }
    }
    

    That alone is a little silly, because the mapping to the actual characters is lost. However, it can be supplied by an array:

    #include <iostream>
    
    enum class AllowedCharNames : char
    {
        ForwardSlash,
        BackSlash,
        VerticalSlash,
        Dash,
        SIZE
    };
    
    char AllowedChars[] = {'/','\\','|','-'};
    
    int main() {
        std::cout << AllowedChars[static_cast<size_t>(AllowedCharNames::Dash)];
        for (int i=0;i< static_cast<int>(AllowedCharNames::SIZE); ++i){
            std::cout << AllowedChars[i];
        }
    }
    

    TL;DR Reconsider if an enum is the right tool for the job. Enums are often overestimated for what they can really do. Sometimes not an enum is the better alternative.