tags:

views:

129

answers:

5

I want to change some Perl code into C++. I need to know how to implement nested Perl hashes in C++. I thought of STL as a good choice and used maps. With the help of maps I can create only a simple hash but I do not know how to create a nested hash structure.

My Perl hash is like this:

%foo = (
    "bar1" => {
        Default => 0,
        Value   => 0
    },
    "bar2" => {
        Default => 2,
        value   => 5,
        other   => 4
    }
)

I can modify it thus: $foo{"bar1"}->{"Default"} = 15.

How do I do this in C++ using STL? Maybe this is a simple question but I am not able to figure it out.

+1  A: 

You can have nested hashes with std::map<key_t,std::map<key_t,value_t> >.

Cedric H.
I edited your post to add a space between the >>, as that will unfortunately not compile (it thinks it's operator>>). If I'm not mistaken 0x will fix that but until then...
Niki Yoshiuchi
I have been thinking about it, it's "fixed" in 0x but in this case it is better to be clear; thx.
Cedric H.
+4  A: 

You may need the type:

std::map< std::string, std::map<std::string, int> > 

You may need to use struct (or class) instead.

struct Element {
    int default;
    int value;
    int other;
    Element(): default(0), value(0), other(0)
    { }
    Element(int default_, int value_, int other_)
    : default(default_)
    , value(value_)
    , other(other_)
    { }
};

int main() {
    std::map<std::string, Element> elements;
    elements["bar1"]; // Creates element with default constructor
    elements["bar2"] = Element(2,5,4);
    elements["bar3"].default = 5; // Same as "bar1", then sets default to 5
    return 0;
}
Notinlist
+2  A: 

As Steve Townsend noted, std::map is similar in concept but has a different implementation.

Creating your nested container in C++ is a little more verbose:

#include <tr1/unordered_map>
#include <iostream>

typedef std::tr1::unordered_map< std::string, int >   Inner;
typedef std::tr1::unordered_map< std::string, Inner > Outer;

int main()
{
  Outer foo;

  Inner::value_type bar1_data[] = {
    Inner::value_type("Default", 0),
    Inner::value_type("Value",   0),
  };
  const size_t n_bar1_data = sizeof(bar1_data) / sizeof(*bar1_data);

  foo["bar1"] = Inner(bar1_data, bar1_data + n_bar1_data);

  Inner::value_type bar2_data[] = {
    Inner::value_type("Default", 2),
    Inner::value_type("value",   5),
    Inner::value_type("other",   4),
  };
  const size_t n_bar2_data = sizeof(bar2_data) / sizeof(*bar2_data);

  foo["bar2"] = Inner(bar2_data, bar2_data + n_bar2_data);

As documented in perlref, arrows between subscripting brackets are optional, so you could have written (commented to maintain the flow of the C++ program)

  // $foo{"bar1"}{"Default"} = 15;

which is pretty close to C++:

  foo["bar1"]["Default"] = 15;

For good measure, we print the resulting structure and return 0 from main:

  for (Outer::const_iterator o = foo.begin(); o != foo.end(); ++o) {
    std::cout << o->first << ":\n";
    for (Inner::const_iterator i = o->second.begin(); i != o->second.end(); ++i)
      std::cout << "  - " << i->first << " => " << i->second << '\n';
  }

  return 0;
}

Output:

bar1:
  - Value => 0
  - Default => 15
bar2:
  - Default => 2
  - value => 5
  - other => 4

NOTE: Input and output have the same order in this toy program, but don't depend on this behavior!

If instead you prefer to use boost::unordered_map, change a few lines at the top of your program:

#include <boost/unordered_map.hpp>

typedef boost::unordered_map< std::string, int >   Inner;
typedef boost::unordered_map< std::string, Inner > Outer;
Greg Bacon
Not being a big C++ user, tr1 is the preview of the new STL, while boost is a third party replacement for much of the functionality?
Evan Carroll
@Evan [TR1](http://en.wikipedia.org/wiki/C%2B%2B_Technical_Report_1) is a set of proposed additions to the C++ standard library, several of which are based on [Boost](http://www.boost.org/) libraries. You can think of Boost as being a proving ground for future additions to the C++ standard library.
Greg Bacon
A: 

Let me add some meta-info to the question. Others have provided actual working code samples.

Apart from being a different kind of container (O(log(n) access for C++ maps vs. typically O(1) for Perl hashes), the prime difference between C++ maps and Perl hashes lies in the static vs. dynamic type distinction of the languages themselves. A map's template arguments clearly specify the type of key and value, whereas for Perl hashes, only the key has a known type (a string). The value is a scalar, but that could and usually does contain any kind of data structure. Thus, it is very cumbersome to use C++ maps for a fairly ordinary Perl data structure such as this:

{
  foo => [qw(bar baz)],
  quibble => {
    lion => 'animal',
    pine => 'plant',
  },
  count => 5,
}

This is more of a practical distinction than a conceptual one because you can always define a struct/class/type that will contain arbitrary stuff (see other answers). It's simply much more work and ends up being quite cumbersome to use.

Using a simple templated type such as map<string, map<string, Stuff> > is really only possible if the structure of your data is very regular.

tsee
A: 

How do I do this in C++ using STL? Maybe this is a simple question but I am not able to figure it out.

The way you use the Perl's hash is the usual way to mimic C/C++'s struct.

Thus I think the closest C++ translation of your code is:

struct foobar {
   int Default, value, other;

   foobar(int Default_ = 0, int value_ = 0, int other_ = 0)
     : Default(Default_), value(value_), other(other_)
   {}
};

std::map< std::string, foobar > foo;

foo["bar1"] = foobar( 0, 0 );
foo["bar2"] = foobar( 2, 5, 4 );
Dummy00001