Reputation: 783
I have a std::unordered_map
container, where Key
can be of two data types:
But value is an object type that is the same with both the key types.
One thing I've tried is making the key a std::variant
so that it can hold both the types. Based on some condition check, key is being set to one of the types:
void A::a() {
std::varaint<type1, type2> Id; //key
if (condition) {
Id = 64 bit unsigned value;
}
else {
Id = tuple<.....>;
}
}
unorderedmap[Id] = obj1;
// ^-- gives compile-time error
// (expecting Id specialized to either of the variant types)
Also, similar to this function there are multiple functions where we are doing find() on unordered_map
.
unorderedmap.find(Id);
// ^-- Here also, compiler is throwing similar error
Is there a way to fix the std::variant, or should I use another approach?
Upvotes: 0
Views: 629
Reputation: 4192
This seems to work just fine:
#include <iostream>
#include <unordered_map>
#include <string>
#include <variant>
typedef std::variant<int, std::string> mytype;
std::unordered_map<mytype, int> m;
int main()
{
m[5] = 20;
std::cout << m[5];
m["hey"] = 10;
std::cout << m["hey"];
mytype tmp = "hey";
std::cout << m[tmp];
}
So the answer is basically: Make sure if you try indexing a map with a variant, the map's index is of the same variant type. If you use get
or this, you can even get it to work when map
is a superset of the variant you want to use - closely emulating dynamic languages.
If you want to support std::tuple
, you have a couple options.
Just use std::map
instead of std::unordered_map
. It's unlikely you'll ever be able to see the logN
, and from experience std::map
will actually be faster (You also won't be murdered by rehashes that take a century, which happens every time std::unordered_map
has to grow).
Keep using std::unordered_map
, but implement hashing. An example is here, with the following adapted code:
#include <iostream>
#include <string>
#include <variant>
#include <unordered_map>
// #include "custom_tuple.h"
// CUSTOM_TUPLE.h
#include <tuple>
namespace std{
namespace
{
// Code from boost
// Reciprocal of the golden ratio helps spread entropy
// and handles duplicates.
// See Mike Seymour in magic-numbers-in-boosthash-combine:
// https://stackoverflow.com/questions/4948780
template <class T>
inline void hash_combine(std::size_t& seed, T const& v)
{
seed ^= hash<T>()(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
}
// Recursive template code derived from Matthieu M.
template <class Tuple, size_t Index = std::tuple_size<Tuple>::value - 1>
struct HashValueImpl
{
static void apply(size_t& seed, Tuple const& tuple)
{
HashValueImpl<Tuple, Index-1>::apply(seed, tuple);
hash_combine(seed, get<Index>(tuple));
}
};
template <class Tuple>
struct HashValueImpl<Tuple,0>
{
static void apply(size_t& seed, Tuple const& tuple)
{
hash_combine(seed, get<0>(tuple));
}
};
}
template <typename ... TT>
struct hash<std::tuple<TT...>>
{
size_t
operator()(std::tuple<TT...> const& tt) const
{
size_t seed = 0;
HashValueImpl<std::tuple<TT...> >::apply(seed, tt);
return seed;
}
};
}
// END CUSTOM_TUPLE.h
typedef std::variant<std::string, std::tuple<int, bool>> mytype;
std::unordered_map<mytype, int> m;
int main()
{
m[std::tuple{5, false}] = 20;
std::cout << m[std::tuple{5, false}];
m["hey"] = 10;
std::cout << m["hey"];
mytype tmp = "hey";
std::cout << m[tmp];
}
You can put everything inside of the namespace std{}
part inside of a header, and then just include that header wherever you want (I omitted include guards, so ofc add that as usual). If the standard ever catches up and implements tuple hashing, just remove the header file.
Upvotes: 3