C++11 Enum class as vector / hashmap keys

closed account (o3hC5Di1)
Hi there everyone,

I've been reading around regarding the strongly typed enums in C++11.
Peter87 had a similar problem to what I'm facing in this topic: http://www.cplusplus.com/forum/general/74363/
However, I need a more general solution applying to more than just one enum.

What I want to do:

1
2
3
4
5
6
7
8
9
10
11
struct example {
    enum class status { SUCCESS, FAIL_DB, FAIL_UNIQUE };

    status some_func()
    {
        std::vector<std::string> errormessages;
        errormessages[status::SUCCESS] = "Operation successful";
        // etc.
        return status::SUCCESS;
    }
};


This does not work because strongly typed enums are not implicitly converted into ints / size_t. The same problem occurs with an unordered_map for pretty much the same reason. Looking at the STL file for std::map it seems that this doesn't require any specific type of key (like size_t for std::vector), and off course doesn't need to hash the key type either, it just sorts using the value.


The workarounds:

As far as I can see there are some workarounds for this:

- Use the plain old enum type (although that causes segmentation faults, but that may be me doing something wrong at the moment).
- Use static_cast: errormessages[static_cast<size_t>(status::SUCCESS)] (which seems too verbose for providing this in a library).
- Define constants instead of an enum: (but then the whole thing becomes less transparent because functions could not return type example::status, but have to return ints).

1
2
3
4
5
6
7
8
9
10
11
12
struct example 
{
    static const int SUCCESS = 0;
    static const int FAIL_DB = 1;

    example()
    {
        std::vector<std::string> errormessages;
        errormessages[status::SUCCESS] = "Operation successful";
        // etc.
    }
};



The question:

Are there any other workarounds and which would be the best one to use (mostly from a library-user perspective)? I suppose using "plain" enums is now not recommended any more because there is a more type safe alternative? Would it be a good practice to wrap enum behaviour in some custom class(es)? Or is there another simple way to implement this kind of transparent functionality that I'm not aware of?

For clarity, at the moment I anticipate only using this behaviour for this purpose, viz. status codes (which would change depending on the operation being performed).

Any thoughts and / or suggestions would be much appreciated.

All the best,
NwN
What about:
1
2
enum class status { SUCCESS, FAIL_DB, FAIL_UNIQUE };
typedef std::map<status, std::string> status_text_t;

Then you can write code like:
 
errormessages[status::SUCCESS] = "Operation successful";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct example
{
    enum class status : int { SUCCESS = 0, FAIL_DB = 1, FAIL_UNIQUE = 2 };

    static std::vector<std::string> errormessages ;

    struct status_less
    {
        bool operator() ( status a, status b ) const { return int(a) == int(b) ; }
    } ;
    static std::map< status, std::string, status_less > map ;

    struct status_hash_eq
    {
        std::size_t operator() ( status a ) const { return std::hash<int>()( int(a) ) ; }
        bool operator() ( status a, status b ) const { return int(a) < int(b) ; }
    } ;
    static std::unordered_map< status, std::string, status_hash_eq, status_hash_eq > hash_map ;

    status some_func()
    {
        errormessages[ int(status::SUCCESS) ] = "Operation successful";

        map[ status::SUCCESS ] = "Operation successful" ;

        hash_map[ status::SUCCESS ] = "Operation successful" ;

        return status::SUCCESS;
    }
};

Last edited on
closed account (o3hC5Di1)
Hi Guys,

Thanks very much for your replies.

@kbw: I know it works with a regular map, because it accepts any type as a key (unlike vectors) and does not do any hashing (unlike unordered_map). However, in this case I would not need any sorting, so I was looking into non sorting alternatives. Thanks for your input though.

@JLBorges Thanks very much for that, basically I would have to provide a hash function object which does the casting. Users would need to be aware of this when creating the hashmap, but that would just have to be documented properly I suppose.

So what about sequential containers like std::array and std::vector, would there be any user-friendly workaround to use strongly typed enums as indices on those?

Thanks again,

All the best,
NwN
> Users would need to be aware of this when creating the hashmap,
> but that would just have to be documented properly I suppose.

Relying on up-to-date documentation that is read, understood and applied correctly by every user? IMHO, not a very good idea.

Something like this, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct example
{
    enum class status : int { SUCCESS = 0, FAIL_DB = 1, FAIL_UNIQUE = 2 };

    struct status_hash_eq
    {
        std::size_t operator() ( status a ) const { return std::hash<int>()( int(a) ) ; }
        bool operator() ( status a, status b ) const { return int(a) == int(b) ; }
    };

    template < typename MAPPED_TYPE,
               typename ALLOCATR_TYPE = std::allocator< std::pair<const status,MAPPED_TYPE> > >
        using status_hash_map_type = std::unordered_map< status, MAPPED_TYPE,
                                        status_hash_eq, status_hash_eq, ALLOCATR_TYPE > ;
    // ..
};

And then:

1
2
3
4
5
6
7
void foo()
{
    // key_type is example::status, mapped_type is std::string
    example::status_hash_map_type<std::string> status_message_map ;
    status_message_map[example::status::FAIL_UNIQUE] = "too bad" ;
    // ...   
}



> would there be any user-friendly workaround to use strongly typed enums as indices on those?

No good work-around that I can think of.

One idea of strongly typed enums is restriction of scope; the other idea is that users should not be thinking of these as numeric types at all.
closed account (o3hC5Di1)
Thanks very much for that JLBorges, that does seem like a clean way to solve this from the client's point of view.

I was thinking maybe the following could do the trick for vectors and arrays:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct example
{
    typedef int status_t;
    
    struct status {
        static const int SUCCESS = 0;
        static const int FAIL_DB = 1;
        static const int SIZE = 2;
    };
};

std::vector<std::string> error_messages;
error_messages.reserve(example::status::SIZE);
error_messages[example::status::SUCCESS] = "foo";


However, that's pretty messy and probably a maintenance nightmare, not mentioning the client having to call .reserve() or use ::SIZE as a template argument to std::array.

Probably it will be best to modify JLBorges solution so it will fit my particular problem best.

Thanks very much once again.

All the best,
NwN
Topic archived. No new replies allowed.