c++: variadic nested maps aka dictionary - c++

I'd like to have a container in cpp that kind of behaves like python dictionaries do, at least in the following regards:
key-value structure (key might be restricted to a single type)
variadic value type (might be restricted to some pod types[int, double, string])
deeply nested (arbitrary depth, but not necessary dynamic)
randomly accessed
accessed type is stored type
So at best, the following example should work;
typedef DictionaryContainer<string, <int, string, bool>, 2> Dict;
Dict mydict; //key-type is string, value-type one of int, string or bool, nested to depth 2
mydict["a"] = 1;
mydict["b"] = true;
mydict["c"] = 'string';
mydict["d"] = Dict();
mydict["d"]["a"] = false;
std::any& dict_d_a = mydict["d"]["a"]; // stores a bool
std::string& dict_c = mydict["c"]; // possibly get<std::string>(mydict["c"])
Is there anything like this out there? I have found containers that allow storing arbitrary types, like boost::variant but they do not allow random access and recursively defined containers (as far as I have figured it out).
Is there not some implementation of a tree / map that can be used in such way?

Related

How to retrieve element from boost::multi_index_container

I'm trying to retrieve a value from a boost::multi_index_container, using a unique numerical id index. I've never used boost::multi_index_container before so I have some trouble understanding how they work. They seem to work a bit like a database, all I want to do is to retrieve an item by specifying the id. Any help would be greatly appreciated.
This is the datatype:
typedef boost::multi_index_container<
// Multi index container holds pointers to the subnets.
Subnet6Ptr,
// The following holds all indexes.
boost::multi_index::indexed_by<
// First is the random access index allowing for accessing
// objects just like we'd do with a vector.
boost::multi_index::random_access<
boost::multi_index::tag<SubnetRandomAccessIndexTag>
>,
// Second index allows for searching using subnet identifier.
boost::multi_index::ordered_unique<
boost::multi_index::tag<SubnetSubnetIdIndexTag>,
boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
>,
// Third index allows for searching using an output from toText function.
boost::multi_index::ordered_unique<
boost::multi_index::tag<SubnetPrefixIndexTag>,
boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
>
>
> Subnet6Collection;
The Subnet6Collection object is created when a dhcpv6-server (KEA) loads its config file. This file contains an optional numerical id value for each subnet, SubnetID in the datatype.
I would like to retrieve a Subnet6Ptr by specifying SubnetID.
Yes, Mutli-index can be a difficult beast to work with. As I wrote in a different answer, "Boost.Multi-index offers an extremely customisable interface, at the cost of offering an extremely complex interface."
Basically, when you want to access the contents of the container, you do it through one of its indices. You therefore start by obtaining a reference to the index which you want to use (the on tagged SubnetSubnetIdIndexTag in your case), and then treat that index pretty much like a container. Which container that is depends on the index's type. For an oredered unique index (as in your case), that would be somewhat like std::map (but with iterators pointing to values only), or like std::set with a transparent comparator which only compares IDs.
Here's what it looks like in code:
Subnet6Collection coll = something();
SubnetID idToLookFor = something2();
auto& indexById = coll.index<SubnetSubnetIdIndexTag>();
auto it = index.find(idToLookFor);
if (it != index.end()) {
Subnet6Ptr p = *it;
} else {
// No such ID found
}
Thanks for answering.
I tried the following (SubnetID is just uint32_t so I used 10 for test):
SubnetID id = 10;
Subnet6Collection coll;
auto& indexById = coll.index<SubnetSubnetIdIndexTag>();
auto it = index.find(id);
if (it != index.end()) {
Subnet6Ptr p = *it;
} else {
// No such ID found
}
but it does not compile:
Opt18_lease_select.cc:38:24: error: invalid use of ‘struct boost::multi_index::multi_index_container, boost::multi_index::indexed_by >, boost::multi_index::ordered_unique, boost::multi_index::const_mem_fun >, boost::multi_index::ordered_unique, boost::multi_index::const_mem_fun, &isc::dhcp::Subnet::toText> > > >::index’
auto& indexById = coll.index<SubnetSubnetIdIndexTag>();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Opt18_lease_select.cc:39:17: error: overloaded function with no contextual type information
auto it = index.find(id);
^~~~
Opt18_lease_select.cc:40:17: error: overloaded function with no contextual type information
if (it != index.end()) {
^~~
Seems like index() find(), end() can't be used this way, or maybe I'm just missing some header file?

Same key, multiple entries for std::unordered_map?

I have a map inserting multiple values with the same key of C string type.
I would expect to have a single entry with the specified key.
However the map seems to take it's address into consideration when uniquely identifying a key.
#include <cassert>
#include <iostream>
#include <string>
#include <unordered_map>
typedef char const* const MyKey;
/// #brief Hash function for StatementMap keys
///
/// Delegates to std::hash<std::string>.
struct MyMapHash {
public:
size_t operator()(MyKey& key) const {
return std::hash<std::string>{}(std::string(key));
}
};
typedef std::unordered_map<MyKey, int, MyMapHash> MyMap;
int main()
{
// Build std::strings to prevent optimizations on the addresses of
// underlying C strings.
std::string key1_s = "same";
std::string key2_s = "same";
MyKey key1 = key1_s.c_str();
MyKey key2 = key2_s.c_str();
// Make sure addresses are different.
assert(key1 != key2);
// Make sure hashes are identical.
assert(MyMapHash{}(key1) == MyMapHash{}(key2));
// Insert two values with the same key.
MyMap map;
map.insert({key1, 1});
map.insert({key2, 2});
// Make sure we find them in the map.
auto it1 = map.find(key1);
auto it2 = map.find(key2);
assert(it1 != map.end());
assert(it2 != map.end());
// Get values.
int value1 = it1->second;
int value2 = it2->second;
// The first one of any of these asserts fails. Why is there not only one
// entry in the map?
assert(value1 == value2);
assert(map.size() == 1u);
}
A print in the debugger shows that map contains two elements just after inserting them.
(gdb) p map
$4 = std::unordered_map with 2 elements = {
[0x7fffffffda20 "same"] = 2,
[0x7fffffffda00 "same"] = 1
}
Why does this happen if the hash function which delegates to std::hash<std::string> only takes it's value into account (this is asserted in the code)?
Moreover, if this is the intended behaviour, how can I use a map with C string as key, but with a 1:1 key-value mapping?
The reason is that hash maps (like std::unordered_map) do not only rely on the hash function for determining if two keys are equal. The hash function is the first comparison layer, after that the elements are always also compared by value. The reason is that even with good hash functions you might have collisions where two different keys yield the same hash value - but you still need to be able to save both entries in the hashmap. There are various strategies to handle that, you can find more information on looking for collision resolution for hash maps.
In your examples both entries have the same hash value but different values. The values are just compared by the standard comparison function, which compares the char* pointers, which are different. Therefore the value comparison fails and you get two entries in the map. To solve your issue you also need to define a custom equality function for your hash map, which can be done by specifiying the fourth template parameter KeyEqual for std::unordered_map.
This fails because the unordered_map does not and cannot solely rely on the hash function for the key to differentiate keys, but it must also compare keys with the same hash for equality. And comparing two char pointers compares the address pointed to.
If you want to change the comparison, pass a KeyEqual parameter to the map in addition to the hash.
struct MyKeyEqual
{
bool operator()(MyKey const &lhs, MyKey const &rhs) const
{
return std::strcmp(lhs, rhs) == 0;
}
};
unordered_map needs to be able to perform two operations on the key - checking equality, and obtaining hash code. Naturally, two unequal keys are allowed to have different hash codes. When this happens, unordered map applies hash collision resolution strategy to treat these unequal keys as distinct.
That is precisely what happens when you supply a character pointer for the key, and provide an implementation of hash to it: the default equality comparison for pointers kicks in, so two different pointers produce two different keys, even though the content of the corresponding C strings is the same.
You can fix it by providing a custom implementation of KeyEqual template parameter to perform actual comparison of C strings, for example, by calling strcmp:
return !strcmp(lhsKey, rhsKey);
You didn't define a map of keys but a map of pointers to a key.
typedef char const* const MyKey;
The compiler can optimize the two instances of "name" and use only one instance in the const data segment, but that can happen or not. A.k.a. undefined behavior.
Your map should contain the key itself. Make the key a std::string or similar.

unordered_map of different custom types

Suppose if I have these two enums:
enum Type
{
Type1,
Type2
};
enum Location
{
Location1,
Location2,
Location3
};
Now I would like to have a container that I can reference like container[Type1][Location1] = 5;
I don't need the elements to be sorted but I need to be able to have duplicates like container[Type1] can be either Location1 or Location2 etc.
I was thinking to use an unordered_multimap<pair<Type, Location>, unsigned int>> which kind of provides what I want, but does not allow me to access the elements as described (or at least I don't know how to do that)
What suggestions do you have?
I believe you're looking for nested maps:
std::unordered_map<Type, std::unordered_map<Location, unsigned int>>

Registry of different types of data

I want to keep some kind of container where a type maps to one value of the type. So essentially what I want is a std::map<std::typeindex, T> where T depends on the type I index it with. std::map doesn't look like a nice way of doing this because the types are rigid. What is the simplest solution I can use for doing this?
If you map to a type-erased container like boost::any, you can at least recover the type if you know what it is:
std::map<std::typeindex, boost::any> m;
m[typeid(Foo)] = Foo(1, true, 'x');
Foo & x = boost::any_cast<Foo&>(m[typeid(Foo)]);
You could use a shared_ptr<void>:
std::map<std::typeindex, std::shared_ptr<void>> m;
m[typeid(T)] = std::make_shared<T>(...);
auto pT = std::static_pointer_cast<T>(m[typeid(T)]); // pT is std::shared_ptr<T>
Or course you would add some wrapper to ensure that the two Ts per line match and you don't accidentially access an empty shared_ptr<void>.

Can we hold 2 data types in a STL list?

i want my list to hold an integer value as well as a string value. is this possible?
I am implementing a hash table using STL lists which can store only the integer. I am hashing a string to get the index where i am storing my integer. Now i want my string to be stored with the integer as well.
EDIT 1:
so i am using this statement:
list<pair<int,string>> table[127];
and here is the error im getting:
>>' should be> >' within a nested template argument list
ok i fixed this.. it seems i didn't put a space in the ">>" so now its fix
next question
how do i add my pair to the table array?
You can have a list of std::pairs or, with c++11, std::tuple, for example:
std::list < std::pair< int, std::string > >list;
std::list < std::tuple< int, std::string > >list;
To access the elements inside a pair, use pair.first and pair.second. To access the elements inside a tuple, use std::get:
auto t = std::make_tuple(1,"something");
std::get<0>(t);//will get the first element of the tuple
You can use std::pair or std::tuple,
std::list<std::pair<int, string>> list;
You can store the string and the integer in a structure and store the objects of the structure.
Each list element can look like:
struct element {
string str;
int val;
};
This is the C way to handle, please #SingerOfTheFall's answer also.