4

Is it possible to define (in a simple way, possibly re-using std container) an "associative std::tuple", or said in other words a "variadiac std::map".

Something like this (this interface is just to explain, other possible interfaces are welcome):

AssociativeTuple<std::string> at;    // std:string is the key type
at.insert<float>("my_float", 3.14);  // 1.
at.insert<int>("my_int", 42);
at.insert<bool>("my_bool", true);
at.insert<int>("xyz", 0);
at.insert<std::string>("my_string", "hello world!");

assert(get(at, "my_float") == 3.14);  // 2.
assert(get(at, "my_int") == 42);
assert(at["my_string"] == "hello world!");  // 3.

assert(std::is_same<at.type_of("my_float")::type, float>)  // 4.

for (auto it : at) { std::cout << it.first << " = " << it.second; }  // 5.

Other desirable constraints:

  1. The set of values/keys are known only at running time. But at compile time the user know the relation between (the type of the values) and keys. For example the user know at compiling time that "my_float" will be a float. To say in another way the set possible of keys is fixed and the type of the value corresponding to a key is known at compile time. What is not known at compiling time is "if" a key will be inserted inside the container. Of course the value of the map value is not known at compiling time.
  2. Access performance, get should be fast
  3. The user don't have to remember the type associated to a key

My real problem is just with value of float/int/bool type (and what I am doing is to store in everything in a std::map<std::string, float> and converting to int when necessary), but a general solution is desirable. In my real case the keys are always std::string.

Ruggero Turra
  • 16,929
  • 16
  • 85
  • 141

3 Answers3

3

You probably mean a map with polymorphic values. For polymorphic values you can use boost::any or, better, boost::variant<>. E.g.:

typedef boost::variant<int, double, std::string> MyVariant;
typedef std::map<std::string, MyVariant> MyPolymorphicMap;
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Well, the "The user don't have to remember the type associated to a key" makes the version with `any` not possible, right? – JBL Mar 10 '15 at 09:26
  • @JBL I would not say so. You can get type information from `boost::any`, it is just that type information is not very useful on its own. Practically speaking `boost::variant<>` is much more useful. – Maxim Egorushkin Mar 10 '15 at 09:34
  • You are declaring `MyVariant` to be a `boost::variant` with three template argument. Why only 3 and not infinite? I have better rewritten what is known at compile time. – Ruggero Turra Mar 10 '15 at 09:59
  • @RuggeroTurra In your original question you had values of only three types: `int`, `double` and `std::string`. You should be able to change the definition to include any types you require. (Obviously, I could not put an infinite list here, because there is no infinite storage for such a list on this website), – Maxim Egorushkin Mar 10 '15 at 10:59
1

How about this? (note: the values are not tuples; you have one value per key).

template<class K> using AnyMap = std::map<K, boost::any>;

AnyMap map;
map["test1"] = 124;
map["test2"] = std::string{ "some text" };

auto value = boost::any_cast<int>(map["test1"]);
utnapistim
  • 26,809
  • 3
  • 46
  • 82
  • is it possibile to fulfill the requirement `3. The user don't have to remember the type associated to a key`? I refer to `boost:any_cast` – Ruggero Turra Mar 10 '15 at 09:46
  • Yes and no; `boost::any` has an accessor for the stored type, but this returns a `std::typeid` (which is quite limited). `boost::variant` is a bit more flexible because of this (you can do more operations on a `variant` than an `any`). – utnapistim Mar 10 '15 at 09:54
0

Yes, this is possible using a variant (plenty of implementations out there) or in general some kind of wrapper, either based on a union (preferred way in your case) or derived from a common base class.

For things like get(at, "my_int") == 42 you would have to overload the equality operator bool operator==(Variant&, int) but that should also not be a problem.

You cannot do stuff like std::is_same<at.type_of("my_float")::type, float> , as is_same is a compile time expression, but the type (you are looking for) is only known at runtime. You can, however, still define a runtime function that performs that check.

However: If - in your specific case - you need only int and float and you are not hard pressed for memory (using strings as keys seem to indicate this), then I'd just used a double as it can represent any number you possibly want to store.

The other possibility would be to use two separate data structures (one for int and one for float). If necessary, you could also build a rwarpper around those in order to make them appear like a single one. Using multiple datastructures might be especially meaningfull, if your programlogic always knows the type associated with a key before performing the lookup anyway.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • please do not recommend usage of `double` as an alternative to storing all types of numbers. `double` (and `float`) are stored in floating point, which [looses precision](http://stackoverflow.com/questions/2909427/c-floating-point-precision) with most computation results. One other thing: you would not need to overload the equality operator for comparing the result of `get(variant_type, key)` with a number. – utnapistim Mar 10 '15 at 09:33
  • @utnapistim: Why not the overload? Also, as double has a precision of ~15 decimal digits and `int` only ~10, each integer number and each float number can be exactly represented by a double – MikeMB Mar 10 '15 at 09:37
  • Strike "exactly", what I meant was without a "loss of accuracy" – MikeMB Mar 10 '15 at 09:39
  • what about 64bit compilation (64bit integers are not representable as doubles - this gets tricky when you use some integer types, like `std::size_t`)? Regarding the overload, the `get` function would return the value, so in the end, you are comparing two ints (no overload using `Variant&` required). – utnapistim Mar 10 '15 at 09:47
  • @utnapistim: I said `int` and not `std::size_t`. While it is not guaranteed by the standard, on almost any "relevant" platform I know of (especailly x64), `sizeof(int)` is 4. How is a general `get` function supposed to return a int or double, when at compiletime, it only knows, it will access a variant? To my knowledge this can only work, if you'd make get a template function and directly specify the type e.g. `get()`, which is not what was asked. – MikeMB Mar 10 '15 at 10:12