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?

4
  • 1
    enums have a huge gap between how we would like them to be and what they really are ;). Would it be an option to have consecutive values for the enumerators (and supply the mapping to chars differently) ? Commented Jan 26, 2022 at 14:42
  • @463035818_is_not_a_number I don't get it. Could you give more explanations? Commented Jan 26, 2022 at 14:47
  • "Is there way to iterate" No. Commented Jan 26, 2022 at 14:50
  • 1
    enum class Foo { bar, quux, baz }; then vector<Foo> FooX{Foo::bar, Foo::quux, Foo::baz};. Now you can iterate using FooX of the Foo enumdefs. Commented Jan 26, 2022 at 15:07

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

11 Comments

What about the visibility of my enum class? Should I make it private since no other class is going to access it?
@digito_evo if nobody is going to access it, why use an enum in the first place? Sorry, I dont understand the question (what class?)
I mean AllowedChars
@digito_evo if you used the enum because you thought it help to iterate then don't use it, it doesn't help. If the enum is there for other reasons then thats kind of orthogonal. Making stuff private that doesn't need to be public is good practice, though Im not really sure what you mean because they arent member of some class
The enum class is a member of Foo.
|
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";
    }
}

1 Comment

thats nice, but OPs case is all about the enumerators not being consecutive

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.