-1

How can I use variants as the key in unordered_map?

For example, I'd like to make the following code work.

using VariantType = std::variant<int, std::string, unsigned int>;
std::unordered_map<VariantType, int, $some_hash_function$> m;

How do I implement $some_hash_function$?

dalibocai
  • 2,289
  • 5
  • 29
  • 45
  • This is interest, not a slag. Why in the name of Gobo Fraggle do you want a key of multiple types? That has to be one weird use case. – user4581301 Aug 26 '20 at 23:38
  • Where is "a `vector` of variants"? – Vlad Feinstein Aug 26 '20 at 23:39
  • 1
    Wait, there is already a hash for variant https://en.cppreference.com/w/cpp/utility/variant/hash So maybe you don't need the third parameter at all. All the docs say is that every item in the variant must have a hash function - and yours do. It compiles without the third parameter and if wouldn't if the hash function didn't exist. I just tried it with something that didn't have a hash and it didn't work so I am positive you don't need the third parameter. – Jerry Jeremiah Aug 26 '20 at 23:42
  • 2
    Smells like [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Mansoor Aug 26 '20 at 23:44
  • Demo of @JerryJeremiah 's point: https://godbolt.org/z/db849x Looks like you don't have to do diddly. By the way, Jerry, Might as well formalize that comment as an answer. – user4581301 Aug 26 '20 at 23:54
  • I will now. I hate writing an answer until the comments have resulted in an answer that doesn't have more questions. Usually the OP has requirements not in the question and you don't find out until there is a discussion. – Jerry Jeremiah Aug 27 '20 at 00:01
  • I admit this is a weird one, but that's irrelevant to the answer. You've got it covered both ways. Either they don't need to do anything or they need need to write a `std::hash`, something we can't help with because of a lack of information. But what they have provided says they can use the generic overload. – user4581301 Aug 27 '20 at 00:24
  • @user4581301 I have such a need because the types of the key I use indeed vary. – dalibocai Aug 27 '20 at 02:14
  • @VladFeinstein Sorry, I updated the question. – dalibocai Aug 27 '20 at 02:15
  • When you change a question, make sure you do not invalidate correct answers. Ask a new question instead. – user4581301 Aug 27 '20 at 04:44
  • @user4581301 Good point – dalibocai Aug 27 '20 at 16:22

1 Answers1

4

There is already a hash template specialization for variant:

http://en.cppreference.com/w/cpp/utility/variant/hash

The only condition is that every type in the variant must have a hash function:

The specialization std::hash<std::variant<Types...>> is enabled (see std::hash) if every specialization in std::hash<std::remove_const_t<Types>>... is enabled, and is disabled otherwise.

But all your variant types have default hashes so, for your variant types, it compiles without the third parameter because the standard hash works. However, if you had a type in your variant that did not have a hash function (or an == operator) then it would fail to compile with this error:

error: static assertion failed: hash function must be invocable with an argument of key type

So back to your question:

When the variant types have hash functions:

#include <variant>
#include <unordered_map>
#include <string>
#include <iostream>
using VariantType = std::variant<int, std::string, unsigned int>;
std::unordered_map<VariantType, int> m =
{
 {1, 1},
 {2u, 2},
 {std::string("string"),3}
};
int main()
{
    VariantType v = std::string{"string"};
    std::cout << m[v];
}

You get this output:

Program returned: 0
Program stdout
3

And when not all the variant types have hash functions:

#include <variant>
#include <unordered_map>
#include <string>
#include <iostream>
class UnhashedClass {};
using VariantType = std::variant<UnhashedClass, int, std::string>;
std::unordered_map<VariantType, int> m =
{
 {1, 1},
 {2u, 2},
 {std::string("string"),3}
};
int main()
{
    VariantType v = std::string{"string"};
    std::cout << m[v];
}

You get this output:

Could not execute the program
Compiler returned: 1
Compiler stderr
...
error: static assertion failed: hash function must be invocable with an argument of key type
...

You can try it yourself here:

https://godbolt.org/z/bnzcE9

Jerry Jeremiah
  • 9,045
  • 2
  • 23
  • 32
  • Thanks for the answer. What if the key is a vector of variants? – dalibocai Aug 27 '20 at 03:24
  • @dalibocai I would say that I'm not going to touch the stuff you're having... ahem. How you even expect it to work having a unique variant tuple (in mathematical sense, not in C++ sense) to be associated unambiguously with a value in map? If you want to associate a value with a set of values, do that like databases do, though relationship. – Swift - Friday Pie Aug 27 '20 at 16:42
  • @Swift-FridayPie I think what he is trying to do is the same as how VBA does Collections - you can retrieve a collection item by index or by key - so you need to be able to use an integer or a string. – Jerry Jeremiah Aug 27 '20 at 21:42
  • @dalibocai I am happy to try to figure out a vector of variants but I'm curious how you would use that? Does the variant value you pass to the retrieval function have to match any item in the vector? Or would you be passing a vector to the retrieval function? If you are passing a single variant to the retrieval function having two maps is a good idea: you look up your variant in the map of IDs and then look up the ID in the map that has the values. If you are passing an entire vector to the retrieval function then: https://www.google.com/search?q=std::hash+std::vector+site:stackoverflow.com – Jerry Jeremiah Aug 27 '20 at 21:49
  • @dalibocai Actually, when I suggested two maps for the "passing a single variant to the retrieval function" version maybe you want a map of variant to set::iterator and a set containing the value - maybe there is no need to store an ID at all? – Jerry Jeremiah Aug 27 '20 at 21:55