Message
| To continue with this theme, here are another couple of templated classes for initialising maps (and multimaps I presume).
The problem with maps is that you need pairs of data to initialise them, eg.
map <string, int> m;
m = "nick", 42, "fred", 56;
To make this work the operator comma has to switch between key and values (key in this example is "nick", value is 42).
If they are different types this is comparatively easy, as the compiler can use the type supplied to work out which is which. For instance in the above example, if a string is supplied we simply store it as the "remembered" key, and then when an int is supplied, insert both as a pair.
However if they are the same type, we need to use a boolean flag to switch between key/value/key/value, and thus doing an insert on every second thing supplied.
Unfortunately the "flag to switch" approach will not work if the key and value are different types, because we need different insertion functions (because of the different types), but if they types are the same then we will get a compiler error because we will have two identical functions.
The solution presented below is a little fiddly, and if anyone can present a neater one, please do so.
Basically you have two initialisation routines, "init_mapsame" if the map has the same type for key and data, and "init_mapdiff" if the map has different data types. Simply choose the appropriate one for the type of data in your map.
I couldn't work out a way of making the templates choose the correct one automatically, I got a "fatal compiler error" when I tried it (under Visual C++ 6).
// disable warnings about long names
#ifdef WIN32
#pragma warning( disable : 4786)
#define data_type referent_type
#endif
#include <string>
#include <map>
#include <iostream>
#include <iterator>
using namespace std;
// mapsame is for maps of the same thing
// (eg. map<string, string> or map<int, int> )
// in this case the data arriving in the operator comma must
// alternate between the key and the value
// C is the container type (eg. map)
template < typename C >
class init_mapsame : public C
{
typename C::key_type key; // save key until we have the value
bool have_key; // remember whether we have a key yet or not
public:
// constructor - no key has arrived yet
init_mapsame () : have_key (false) {}
// add to container (eg. m = 1, 100, 2, 200, 3, 300; )
init_mapsame & operator, ( const typename C::data_type & d )
{
if (have_key)
insert (make_pair (key, d));
else
key = d;
have_key = !have_key;
return *this;
}
// append to container (eg. m += 4, 400; )
init_mapsame & operator+= ( const typename C::key_type & k )
{
key = k; // we have the key so far
have_key = true;
return *this;
}
// replace container (eg. m = 8, 800; )
init_mapsame & operator= ( const typename C::key_type & k )
{
clear ();
key = k;
have_key = true;
return *this;
}
}; // end of class init_mapsame
// init_mapdiff is for maps of different things
// (eg. map<string, int> or map<int, float> )
// in this case the data arriving in the operator comma
// can be distinguished (key from data) by the data type
// C is the container type (eg. vector)
template < typename C >
class init_mapdiff : public C
{
typename C::key_type key;
public:
// add to container (eg. m = 1, "a", 2, "b", 3, "d"; )
init_mapdiff & operator, ( const typename C::key_type & k )
{
key = k;
return *this;
}
// add to container (eg. m = 1, "a", 2, "b", 3, "d"; )
init_mapdiff & operator, ( const typename C::data_type & d )
{
insert (make_pair (key, d));
return *this;
}
// append to container (eg. m += 4, "e"; )
init_mapdiff & operator+= ( const typename C::key_type & k)
{
key = k; // we have the key so far
return *this;
}
// replace container (eg. m = 10, "x"; )
init_mapdiff & operator= ( const typename C::key_type & k )
{
clear (); // empty the container
key = k; // we have the key so far
return *this;
}
}; // end of class init_mapdiff
// general routine to show a map container's contents
template <typename T>
void showpairs (const string & prefix,
const T & container,
const string & separator = "=",
const string & delim = " ",
const string & suffix = "\n\n")
{
cout << prefix; // initial description
for (typename T::const_iterator iter = container.begin ();
iter != container.end ();
iter++)
cout << iter->first << separator << iter->second << delim;
cout << suffix; // final description (eg. \n )
} // end of showpairs
int main (void)
{
init_mapsame <map<int, int> > m;
m = 123, 456, 222, 789, 555, 666;
showpairs ("m = ", m, "=", ", ");
m += 8, 7;
showpairs ("m after add = ", m, "=", ", ");
init_mapdiff <map<int, float> > m2;
m2 = 123, (float) 84.5, 567, (float) 99.0, 100, (float) 200.0;
showpairs ("m2 = ", m2, "=", ", ");
m2 += 44, (float) 56.7;
showpairs ("m2 after add = ", m2, "=", ", ");
init_mapdiff <map<string, int> > m3;
m3 = "nick", 10, "john", 42, "peter", 123;
showpairs ("m3 = ", m3, "=", ", ");
return 0;
} // end of main
Output
m = 123=456, 222=789, 555=666,
m after add = 8=7, 123=456, 222=789, 555=666,
m2 = 100=200, 123=84.5, 567=99,
m2 after add = 44=56.7, 100=200, 123=84.5, 567=99,
m3 = john=42, nick=10, peter=123,
The line near the start:
#define data_type referent_type
is because STL does not seem to have a standard typename for the data type of a map. The name key_type is standard between Windows and the Linux implementations but on Windows the data (ie. second part of the pair) is called "referent_type" however on Linux it is called "data_type". Later implementations may have different names, you may need to fiddle with, or remove, that define as required.
It might also be worth pointing out that these initialisation routines do not detect "bad data". For example, if you are initialising pairs of things and leave off the last entry, it will not be detected, eg.
init_mapdiff <map<string, int> > m;
m = "nick", 10, "john", 42, "peter";
In this example the key "peter" has been supplied but no value for "peter". Thus, the entry for "peter" will be silently discarded.
It will also not detect situations where you have two keys in a row, or two data elements in a row, eg.
init_mapdiff <map<string, int> > m;
m = "nick", "bruce", 10, "john", 42, 456;
In this example, it will store "nick", but then replace it with "bruce", eventually storing the pair <bruce, 10>.
Then when it gets "john", 42, it will store the pair <john, 42>. However the final 456 will also trigger another pair to be stored, namely <john, 456>.
You could make the classes a bit more complicated to try to detect these situations. However I am expecting that you would normally use them in program initialisation for short lists, and with proper visual layout in the program, such errors should be obvious to the eye.
|
- Nick Gammon
www.gammon.com.au, www.mushclient.com | Top |
|