0

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?

digito_evo
  • 3,216
  • 2
  • 14
  • 42

2 Answers2

1

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

In C++, the enum and enum class do not provide the kind of functionality you want.

You can implement that functionality yourself. There is considerable boilerplate, but it may be worth it since it will make the callsite considerably easier to use.

Depending on the enumeration definitions of an enum or enum class, the implementation routines can accommodate sequences with gaps.

Here is an example, with a simple enum class, that has gaps.

#include <iostream>
#include <stdexcept>
#include <utility>

using std::cout;
using std::logic_error;
using std::ostream;
using std::underlying_type_t;

namespace {


enum class Lorem {
    ipsum = 10, dolor = 20, sit = 30, amet = 100, consectetur = 105, adipiscing = 111
};

auto Lorem_first() -> Lorem { return Lorem::ipsum; }
auto Lorem_last() -> Lorem { return Lorem::adipiscing; }

auto operator<<(ostream& out, Lorem e) -> ostream& {
    switch(e) {
#define CASE(x) case Lorem::x: return out << #x
        CASE(ipsum);
        CASE(dolor);
        CASE(sit);
        CASE(amet);
        CASE(consectetur);
        CASE(adipiscing);
#undef CASE
    }

    throw logic_error("operator<< unknown Lorem");
}

auto operator+(Lorem e) -> underlying_type_t<decltype(e)> {
    return static_cast<underlying_type_t<decltype(e)>>(e);
}

auto operator++(Lorem& e) -> Lorem& {
    if (e == Lorem_last()) throw logic_error("operator++(Lorem&)");
    switch(e) {
#define CASE(x, y) case Lorem::x: e = Lorem::y; break
        CASE(ipsum, dolor);
        CASE(dolor, sit);
        CASE(sit, amet);
        CASE(amet, consectetur);
        CASE(consectetur, adipiscing);
#undef CASE
        case Lorem::adipiscing: break;
    }
    return e;
}

class Lorem_Range {
    bool done = false;
    Lorem iter = Lorem_first();

public:
    auto begin() const -> Lorem_Range const& { return *this; }
    auto end() const -> Lorem_Range const& { return *this; }
    auto operator*() const -> Lorem { return iter; }
    bool operator!=(Lorem_Range const&) const { return !done; }
    void operator++() {
        if (done) {
            throw logic_error("Lorem_Range::operator++");
        }

        if (iter == Lorem_last()) {
            done = true;
        } else {
            ++iter;
        }
    }
};

} // anon

int main() {
    for (auto e : Lorem_Range()) {
        cout << +e << ", " << e << "\n";
    }
}
Eljay
  • 4,648
  • 3
  • 16
  • 27