Reasonably simple way to make enum work as an unordered_map key or value?

Sep 6, 2019 at 1:06am
Hi, I have a struct in which I store some info. Part of it is an enum type of class which specifies what type it is:

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
struct token {

	enum class types {
		NUMBER,
		OPERATOR,
		LEFT_PARENTHESIS,
		RIGHT_PARENTHESIS,
		FUNCTION,
		CONSTANT,
		VARIABLE
	};
	enum class functions {
		PLUS,
		MINUS,
		TIMES,
		DIV,
		POW,
		SIN,
		COS,
		TAN,
		EXP,
		SQRT,
		ABS,
		THETA,
	};
	types type;
	functions function;
	...
};


Let's say I want to quickly be able to convert a string to the enum type and enum type to some function. My sample looks as follows:

1
2
3
4
5
std::unordered_map<token::functions, double(*)(std::vector<token>&), std::hash<token::functions>> type_to_function = {
	{token::functions::PLUS, plus},
	{token::functions::MINUS, minus},
	...
};


or

1
2
3
4
5
6
7
std::unordered_map<std::string, token::functions, std::hash<token::functions>> function_to_type = {
	{"plus", token::functions::PLUS},
	{"+", token::functions::PLUS},
	{"minus", token::functions::MINUS},
	{"-", token::functions::MINUS},
	...
};


Now, this refuses to compile, the following error shows up:

Error C2664 'size_t std::_Conditionally_enabled_hash<_Kty,true>::operator ()(const _Kty &) noexcept(<expr>) const': cannot convert argument 1 from 'const _Kty' to 'const _Kty &'

I googled and tried to add this

1
2
3
4
5
6
struct EnumClassHash {
    template <typename T>
    std::size_t operator()(T t) const {
        return static_cast<std::size_t>(t);
    }
};


before the definition of my token, but when I use EnumClassHash, it complains that:

Error C2440 'static_cast': cannot convert from 'T' to 'size_t'


What is the proper way to implement enum as an unordered map key or value? (I assume that it should even work implicitly, since enum pretty much acts like an integer...)

I'm using VS Community 2017, version 15.9.15

Thank you for any help.
Last edited on Sep 6, 2019 at 2:28am
Sep 6, 2019 at 3:22am
Well I am not sure of the full scope of what you are doing, but for different problems there are differen't containers you need.

The container for enums are obvious, an array or a (filled) vector. Except for strings, you can use string keys with std::map, but I should note that unordered_map is slower than map for small lists, like around less than 20, because std::map isn't a hash map, it just loops through a list or something like that, and it is quite fast.

Also yes enums are plain ints.

function_to_type would work if you removed the 3rd template argument because you are using the 2nd argument as the hash, when the hash is the first argument, also you never need to use the 3rd argument unless you are creating a custom hasher, since std::hash is always used by default when you only have 2 arguments.

For type_to_function, what JLBorges said, I didn't read the error as a hash template error it would help if you shown more, maybe something that compiles, but I bet it is because double(*)(std::vector<token>&) doesn't match the functions "plus" or another, most likely because these functions are not static and are inside of a struct/class without a static on it.
Last edited on Sep 6, 2019 at 4:00am
Sep 6, 2019 at 3:47am
Provide a custom non-throwing hash function and it would work.

For example:

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
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <unordered_map>
#include <vector>

struct token {

    enum class functions {
            PLUS,
            MINUS,
            TIMES,
            DIV
            // ...
    };

    // ...
};

struct tok_hash {

    std::size_t operator() ( token::functions f ) const noexcept {

        return std::size_t(f) ;
    }
};

using function_type = double( std::vector<token> ) ;

using map_type = std::unordered_map< token::functions, function_type*, tok_hash > ;

double my_plus( std::vector<token> ) { return 0 ; }
double my_minus( std::vector<token> ) { return 0 ; }
double my_times( std::vector<token> ) { return 0 ; }

int main() {

    map_type map { { token::functions::PLUS, my_plus } } ;
    map.emplace( token::functions::MINUS, my_minus ) ;
    map[token::functions::TIMES] = my_times ;
    
    std::cout << "ok\n" ;
}

http://coliru.stacked-crooked.com/a/e87022ff6d69dba8
https://rextester.com/DOABT38724

Note: This custom hash function is not required in C++14 or later;
the standard library provides std::hash specialisations for scoped and unscoped enumeration types
Sep 6, 2019 at 2:47pm
poteto: you're completely right, since the enum entries are not used as keys in function_to_type, no additional hash is needed for this specific unordered map :) Since you asked, I'm using these unordered maps to (presumably) quickly look up a function in an infix evaluation, especially when I need to evaluate an expression many, many times. I think it's still too slow (definitely slow, compared to a compiled function), but what can I do :/

JLBorges: Your solution works, thank you! Would you please help me enable C++17 features in Visual Studio? My project properties under C/C++ - Language C++ Language Standard already reads ISO C++17, but the compiler seemingly struggles to find the default hash for enum (which should work like the one for integers, since enums are just integral values)
Sep 6, 2019 at 4:21pm
> I'm using VS Community 2017, version 15.9.15
> Would you please help me enable C++17 features in Visual Studio?

Upgrade to Visual Studio Community 2019.
https://visualstudio.microsoft.com/vs/community/
Sep 7, 2019 at 12:05am
enable C++17 features in Visual Studio

VS 2017 or VS 2019, it is the same way.

Have your solution/project open in the IDE. Bring up the project's property pages, open up the C/C++ submenu. On the Language property page select /std:c++17 or /std:c++latest for the C++ Language Standard.

Both 2017 and 2019are compliant with C++17's core language, there are a few defect reports not addressed.

https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=vs-2019

Upgrade to Visual Studio Community 2019.

I have both 2017 and 2019 installed, and when I fired up 2017 a few minutes ago to check how to set the language standard there was an "alert" to install 2019.

If someone wants C++20 support, then 2019 has more features implemented than 2015 or 2017.
Sep 9, 2019 at 1:40am
Furry Guy, I already did that. Not sure why it wouldn't compile without providing a custom hash. C++17 is supposed to have that implemented already.

But I'll try to upgrade to VS 2019 and see if it helps.
Topic archived. No new replies allowed.