views:

166

answers:

5

How can I handle data in C++ like in newer dynamic languages, for example the arrays in PHP are quite neat:

$arr = array(

"somedata" => "Hello, yo, me being a simple string",

"somearray" => array(6 => 5, 13 => 9, "a" => 42),

"simple_data_again" => 198792,

);

I am open to all suggestions.

+2  A: 

Have a look a 'map' in C++, there's a good Wikipedia article here.

Lazarus
I looked at them, they would be perfect if they could hold another maps inside.
Mike Shinola
+4  A: 

C++ doesn't provide a built-in support for dynamically typed hierarchical data, but you can build one from lower-level primitives or use a third-party library, for example:

  1. std::map with boost::variant or boost::any as value type.
  2. boost::property_tree
Alex B
@Alex B: Can C++ maps really handle all the sample entries in the heterogenous array shown in OP?
Chubsdad
@Chubsdad, As I mentioned, you can do it with using `boost::variant` as a value type (which can hold any type).
Alex B
@Alex B: +1 for boost::variant
Chubsdad
@Alex B: "but you can build one from lower-level primitives"... thats exactly something which I am eager to learn :-D. How could I start with this? Any hints?
Mike Shinola
@Mike: there are 2 hints already in the article, read about them, experiment, and come back with your results if you need any more help.
Matthieu M.
@Matthieu M.: Roger.
Mike Shinola
+1  A: 

boost::variant can hold a "union of types", to be more exact. This is an example of how you could define an expression similar to the requested one:

std::map
<
 std::string,
 boost::variant
 <
  std::string,
  std::array<int, 4>,
  int
 >
> my_variant_array;

my_variant_array["somedata"] = "Hello, yo, me being a simple string";
my_variant_array["somearray"] = {
 1, 2, 3, 4
};
my_variant_array["simple_data_again"] = 198792;

You could even define a recursive variation of such a structure containing another std::map of boost::variant. Please be aware, that this is something different from what you probably have in mind: boost::variant is a type safe union of types with associated values. You could use it, to implement a compiler for a dynamic language which would allow constructs as you requested. c++ per se is not dynamic - everything needs to be strongly typed.

paul_71
But that structure is fixed, right? I need something dynamic. Nevertheless, thanks for the insight on boost::variant.
Mike Shinola
Which structure? The map is not fixed. The boost::variant is fixed, except you suffice to define a recursive variant... To be honest I tried, and haven't made it - just have look at the boost documentation and try it yourself. However, to some extent it will always be fixed. As I tried to explain above, there is no dynamic functionality of that kind in c++, everything needs to have a type known at compile time - even if the type might look a bit weird. Compile the above statements and have a look at the run-time type in the debugger of your choice :)
paul_71
@paul_71: Which structure? - I commented on the boost::variant. I think I will stick with the map, maybe I can get it to behave more or less how I want =).
Mike Shinola
+4  A: 

While I understand the appeal of dynamically typed languages, it doesn't fit well with C++.

C++ is statically typed, which means that the compiler knows which type a variable is going to be and therefore can process it safely.

There are ways to emulate dynamic typing (use of Boost.Any for example), but the real question is WHY ?

If you need dynamic typing and/or reflection, then use a language that support them, you'll be better served.

If you need/want C++, learn how to program in C++ instead of trying to port PHP verbatim.

Most languages can serve the same purpose, it doesn't mean things are processed in the same manner under the core. It's frustrating (at the best of times) to try to port a language to another statement by statement.

Matthieu M.
Didn't see this answer before (maybe I was too busy typing mine :). Anyway, my answer concludes with the same thing - don't try to apply dynamic languages' idioms to a statically typed language. +1 for saying it better than (and before) me. :)
missingfaktor
@Missing Faktor: happens to me everytime :)
Matthieu M.
@Matthieu M: The answer to the "Why": imagine i want to hold some user inputted data, which does not have any predefined structure, nor type nor nothing. One can even input arrays. Like if someone would enter "My favourite foods" than give me two or three items. Others would give one favourite food. Again others would not give me anything about their eating habits, but rather tell me their age. Then at the end I want to be able to retrieve all people whos favorite food is banana. Or whose age is 32. Or who like whitewater rafting. The data values would be identified by user-inputted keys.
Mike Shinola
@Mike: I understand a bit better. Would the data be representable in JSon format ? JSon can represent a whole lot of different things with very few basic types (none, boolean, integer, floating point, string, list, dictionary)... and C++ can definitely parse JSon :) Yet it doesn't require wild "dynamic" typing, Polymorphism and the Visitor pattern are wonderful :)
Matthieu M.
@Matthieu M. JSON is exactly the thing I need. But how does the JSON data is represented in my program? Is that a string-like thing? Or it gets binded into maps or lists? I want to learn how can I represent these freeform data structures in C++ effectively. My aim is to not use a third party library but implement my own (for educational purposes.) Im sure I can solve my problem with some basic pointer mumbo-jumbo, but as you see I am quite a beginner. Thanks.
Mike Shinola
@Mike: in the case of JSon, it's a bit difficult, but not that much. If you know about JavaScript or Python, Json reuses very similar syntax, and as you can see, it's extremely restricted. The simpler solution is to create a base class `Object` and an `enum` `Kind`. Create one different concrete class per `Kind` (none, boolean, number, string, list, dictionary) and then use dynamic_cast to know which type truly was instantiated. You'll need dynamic allocation (`new`), so make sure to use smart pointers (`boost::scoped_ptr` / `std::unique_ptr`) to take ownership.
Matthieu M.
@Matthieu M.: I will try that! Thanks for all your help.
Mike Shinola
+5  A: 

If you know in advance what all kinds of values map is going to hold, then use boost::variant. Or else, use boost::any. With boost::any, you could later add entries with any type of value to the map.


Example code with boost::variant:

Creating a map:

typedef boost::variant<std::string, std::map<int, int>, int> MyVariantType;
std::map<std::string, MyVariantType> hash;

Adding entries:

hash["somedata"] = "Hello, yo, me being a simple string";
std::map<int, int> temp; 
temp[6] = 5; 
temp[13] = 9;
hash["somearray"] = temp;
hash["simple_data_again"] = 198792;

Retrieving values:

std::string s = boost::get<std::string>(hash["somedata"]);
int i = boost::get<int>(hash["simple_data_again"]);

As pointed out by @Matthieu M in the comments, the key-type of the map can be a boost::variant. This becomes possible because boost::variant provides a default operator< implementation providing the types within all provide it.


Example code with boost::any:

Creating a map:

std::map<std::string, boost::any> hash;

Adding entries:

hash["somedata"] = "Hello, yo, me being a simple string";
std::map<int, int> temp; 
temp[6] = 5; 
temp[13] = 9;
hash["somearray"] = temp;
hash["simple_data_again"] = 198792;

Retrieving values:

std::string s = boost::any_cast<std::string>(hash["somedata"]);
int i = boost::any_cast<int>(hash["simple_data_again"]);

Thus, with help of boost::any the value-type in your map can be dynamic (sort of). The key-type is still static, and I don't know of any way how to make it dynamic.


A word of advice:

C++ is a statically typed language. The idioms like the one in your post which are used quite often in dynamic language don't fit well with the C++ language. Going against the grain of a language is a recipe for pain.

Therefore I'd advise you not to try to emulate the idioms from your favorite dynamic languages in C++. Instead learn the C++ ways to do things, and try to apply them to your specific problem.


References:

missingfaktor
@Missing Faktor: that's an elaborate answer! The key type can be dynamic easily, all that is required of it is to support comparison. At the very worst, you'll just have to provide a custom comparison predicate. If I recall correctly, Boost.Variant provides a default `operator<` implementation providing the types within all provide it.
Matthieu M.
@Matthieu: Oh thanks, I'll edit my post to include that.
missingfaktor
@Matthieu: Re: dynamic key-part, it's not possible even with custom comparison predicate, as what all types a key can take is not known at compile time and without this information we cannot write a custom comparison predicate that covers all possible kinds of keys.
missingfaktor
@Missing Faktor: I appreciate your and others detailed answer. Instead asking more questions I go and get my hands dirty and investigate the offered solutions (as pointed out by Matthieu M.). Thanks
Mike Shinola
@Missing Faktor: actually you can compare keys of unknown types, think `void*` :) For example, Boost.Any offers a `type()` method which returns a `type_info` object. You can then use its `name()` method. `bool operator<(boost::any const }`
Matthieu M.