Initializing a map...

B

barcaroller

Is there a way in C++ to initialize an STL map in one statement (the way
arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };
 
P

Phil Endecott

barcaroller said:
Is there a way in C++ to initialize an STL map in one statement (the way
arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };

Boost.Assign has some stuff for this sort of thing.


Phil.
 
J

Jeff Schwab

barcaroller said:
Is there a way in C++ to initialize an STL map in one statement (the way
arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };

There's no special syntax for maps. You do have a few options, though.
One is to initialize an array with the nicer syntax, then initialize
the map from the array.

typedef std::map<type1, type2> map_type;
typedef map_type::value_type pair_type;

template<typename T, std::size_t z>
std::size_t size(T const (&a)[z]) {
return z;
}

int main() {
pair_type initializers[] =
{ pair_type(key1, value1), pair_type(key2, value2) };
map_type m(initializers, initializers + size(initializers));
}

Another option is to create the map within a function, then return it by
value.

map_type create_map() {
map_type result;
result.insert(pair_type(key1, value1));
result.insert(pair_type(key2, value2));
return result;
}

int main() {
map_type map = create_map();
}

A third option is to let the map start out empty, then use a function to
populate it.

void populate(map_type& m) {
m.insert(pair_type(key1, value1));
m.insert(pair_type(key2, value2));
}

int main() {
map_type m;
populate(m);
}
 
S

Sam

barcaroller said:
Is there a way in C++ to initialize an STL map in one statement (the way
arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };

You can subclass it, and define an operator function.


template<typename keyType, typename valType> class myMap
: public std::map<keyType, valType> {

public:
myMap<keyType, valType> &operator()(keyType k, valType v)
{
(*this)[k]=v;
return *this;
};
};

You can initialize these objects as follows:

myMap<int, int> z=myMap<int, int>()(3, 4)(5, 6);

... and so on. You can use these objects anywhere std::map is acceptable.



-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (GNU/Linux)

iD8DBQBHvMUyx9p3GYHlUOIRAr9AAJ0Z1htKkfWKFdWGK/Qi6WDcGYNA3ACbB7mO
8LABhHnbT/QP8M2HfUwxzxM=
=4zSY
-----END PGP SIGNATURE-----
 
J

Jeff Schwab

Sam said:
barcaroller said:
Is there a way in C++ to initialize an STL map in one statement (the
way arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };

You can subclass it, and define an operator function.


template<typename keyType, typename valType> class myMap
: public std::map<keyType, valType> {

public:
myMap<keyType, valType> &operator()(keyType k, valType v)
{
(*this)[k]=v;
return *this;
};
};

You can initialize these objects as follows:

myMap<int, int> z=myMap<int, int>()(3, 4)(5, 6);

.. and so on. You can use these objects anywhere std::map is acceptable.

Augh! std::map is a concrete type, really not meant to be publicly
subclassed. It hasn't got a virtual destructor, for example, so the
following causes undefined behavior:

std::map* p = new myMap;
delete p;

Your idea is good, but either (1) the inheritance should be private, or
(2) myMap should contain the std::map sub-object as a member rather than
a base.
 
R

red floyd

Jeff said:
Sam said:
barcaroller said:
Is there a way in C++ to initialize an STL map in one statement (the
way arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };

You can subclass it, and define an operator function.


template<typename keyType, typename valType> class myMap
: public std::map<keyType, valType> {

public:
myMap<keyType, valType> &operator()(keyType k, valType v)
{
(*this)[k]=v;
return *this;
};
};

You can initialize these objects as follows:

myMap<int, int> z=myMap<int, int>()(3, 4)(5, 6);

.. and so on. You can use these objects anywhere std::map is acceptable.

Augh! std::map is a concrete type, really not meant to be publicly
subclassed. It hasn't got a virtual destructor, for example, so the
following causes undefined behavior:

Or you could use a proxy initializer:


#include <map>

template<typename K, typename V>
class map_initializer_proxy
{
std::map<K, V> map_;

public:
map_initializer_proxy(const K& k, const V& v)
{
map_[k] = v;
}
map_initializer_proxy& operator()(const K& k, const V& v)
{
map_[k] = v;
return *this;
}
operator const std::map<K,V>&() const
{
return map_;
}
};
template<typename K, typename V>
map_initializer_proxy<K,V> map_initializer(const K& k, const V& v)
{
return map_initializer_proxy<K,V>(k,v);
}

#include <iostream>
#include <ostream>

int main()
{
std::map<int, int> m(map_initializer(3,4)(5,6)(7,8));
for (std::map<int, int>::iterator it = m.begin();
it != m.end();
++it)
std::cout << "( "
<< it->first
<< ","
<< it->second
<< " )"
<< std::endl;
return 0;
}
 
J

Jim Langston

Your messages always have the text as an attachment instead of in the body
of the message. Just thought you might want to know.
 
J

Jeff Schwab

red said:
Jeff said:
Sam said:
barcaroller writes:


Is there a way in C++ to initialize an STL map in one statement (the
way arrays can be initialized in C)?

For example, instead of using:

map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;


I would like to use something like:

// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };

You can subclass it, and define an operator function.


template<typename keyType, typename valType> class myMap
: public std::map<keyType, valType> {

public:
myMap<keyType, valType> &operator()(keyType k, valType v)
{
(*this)[k]=v;
return *this;
};
};

You can initialize these objects as follows:

myMap<int, int> z=myMap<int, int>()(3, 4)(5, 6);

.. and so on. You can use these objects anywhere std::map is acceptable.
....
Or you could use a proxy initializer:


#include <map>

template<typename K, typename V>
class map_initializer_proxy
{
std::map<K, V> map_;

public:
map_initializer_proxy(const K& k, const V& v)
{
map_[k] = v;
}
map_initializer_proxy& operator()(const K& k, const V& v)
{
map_[k] = v;
return *this;
}
operator const std::map<K,V>&() const
{
return map_;
}
};
template<typename K, typename V>
map_initializer_proxy<K,V> map_initializer(const K& k, const V& v)
{
return map_initializer_proxy<K,V>(k,v);
}

#include <iostream>
#include <ostream>

int main()
{
std::map<int, int> m(map_initializer(3,4)(5,6)(7,8)); ....
return 0;
}

That's a neat idea. It could probably be made a little more efficient
by replacing the calls to map::eek:perator[] with calls to map::find and
map::insert. If the OP is willing to have two statements rather than
one, a copy of the map's data can also be saved by adding a swap function:

template<typename K, typename V>
struct map_initializer_proxy {

// ...

void swap(std::map<K, V>& m) {
map_.swap(m);
}
};


int main() {
std::map<int, int> m;
map_initializer(3,4)(5,6)(7,8).swap(m);
}

This is admittedly not as nifty, since it's not really an
initialization. It's more like a map_filler.
 
A

Alf P. Steinbach

* Jim Langston:
Your messages always have the text as an attachment instead of in the body
of the message. Just thought you might want to know.

Only for Outlook Express users... ;-)

It seems Outlook Express is the only commonly used newsreader that
doesn't handle digitally signed messages correctly.


Cheers, & hth.,

- Alf
 
J

James Kanze

barcaroller said:
Is there a way in C++ to initialize an STL map in one
statement (the way arrays can be initialized in C)?
For example, instead of using:
map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;
I would like to use something like:
// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };
There's no special syntax for maps.

I think there will be in the next version of the standard. (I
know that there was a proposal for extended initializers, but
I'm not sure what the current status of the proposal was.)
You do have a few options, though.
One is to initialize an array with the nicer syntax, then initialize
the map from the array.

This is the only way to create a const map.
typedef std::map<type1, type2> map_type;
typedef map_type::value_type pair_type;
template<typename T, std::size_t z>
std::size_t size(T const (&a)[z]) {
return z;
}
int main() {
pair_type initializers[] =
{ pair_type(key1, value1), pair_type(key2, value2) };
map_type m(initializers, initializers + size(initializers));
}

I often find it worthwhile to define a special structure for
this, something along the lines of:

typedef std::map< std::string, double > Map ;
struct MapInit
{
char const* key ;
double value ;
operator Map::value_type() const
{
return Map::value_type( std::string( key ), value ) ;
}
} ;

Partially for historical reasons: earlier Microsoft compilers
had problems with agglomerate initialization if the components
of the agglomerate weren't agglomerates themselves. But having
gotten into the habit of it... It's still worth considering if
the initialization array is in a separate compilation unit (e.g.
because it is machine generated), since you can arrange for the
table of MapInit to use static initialization, thus avoiding all
order of initialization issues.
Another option is to create the map within a function, then
return it by value.
map_type create_map() {
map_type result;
result.insert(pair_type(key1, value1));
result.insert(pair_type(key2, value2));
return result;
}
int main() {
map_type map = create_map();
}

Not recommended for big maps in the middle of a tight loop:).
A third option is to let the map start out empty, then use a
function to populate it.
void populate(map_type& m) {
m.insert(pair_type(key1, value1));
m.insert(pair_type(key2, value2));
}
int main() {
map_type m;
populate(m);
}

Neither of the above allow the map to be const, of course.

Another possibility is to derive, with the derived class
providing a constructor which populates the map (and nothing
else). Again, this allows the map to be const. It also works
with older implementations, which didn't always support the two
iterator form of the constructor (because they were designed
around compilers which didn't support member templates). So you
have something like:

class MyMap : public std::map< type1, type2 >
{
public:
MyMap() ;
} ;

MyMap::MyMap()
{
insert( value_type( key1, value1 ) ) ;
insert( value_type( key2, value2 ) ) ;
// ...
}

(Again, you could probably arrange for the constructor to be
machine generated.)

Concerning the "machine generated": if your map has so few
entries that you can consider writing them out by hand, you're
likely better off just using a C style array and std::find_if.
(For a const map, of course. But I can't remember a case where
I wanted to initialize the map's contents on creation, but
didn't want it const, although I'm sure that they do exist.)
 
J

James Kanze

Sam said:
barcaroller writes:
Is there a way in C++ to initialize an STL map in one
statement (the way arrays can be initialized in C)?
For example, instead of using:
map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;
I would like to use something like:
// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };
You can subclass it, and define an operator function.
template<typename keyType, typename valType> class myMap
: public std::map<keyType, valType> {
public:
myMap<keyType, valType> &operator()(keyType k, valType v)
{
(*this)[k]=v;
return *this;
};
};
You can initialize these objects as follows:
myMap<int, int> z=myMap<int, int>()(3, 4)(5, 6);

Or even:

std::map< int, int > z = myMap< int, int >()( 3, 4 )( 5, 6 ) ;

I like it. I'm just afraid that it deviates enough from usual
practice to be a bit of obfuscation. (Maybe you should try to
get something like this into Boost, so that it can be considered
usual practice.)

Note too that as used above, there is a deep copy of the map.
Not necessarily the sort of thing you might want in a tight
loop. (But how often does one construct pre-initialized maps in
a tight loop, anyway?)

The other thing to consider is that it binds the initializer
list very tightly to the actual declaration, in a way which
makes machine generation of the initializers somewhat difficult.
And if the map is small enough that you're willing to consider
writing the initialers out by hand, it's probably small enough
for you to simply use find_if on a C style array.
Augh! std::map is a concrete type, really not meant to be
publicly subclassed.

It's true that std::map isn't designed to be used as a base
class. But you have to weigh everything. It doesn't bother me
in the least to derive from it to provide a specialized
constructor which contains the specific initialization---the
semantics of the resulting class (with its specific
initialization) are such that no one is going to use it except
in its intended use. His class is more general, so perhaps the
risk is greater, but I still find it within the realm of
reasonableness. On the other hand...

std::map basically provides an implementation class for several
more or less distinct use cases. (See the discussions on its
operator[], which can't be used on a const object.) Perhaps the
ideal solution is to define precise interfaces for each of the
use cases, using a member std::map for the implementation.
It hasn't got a virtual destructor, for example, so the
following causes undefined behavior:
std::map* p = new myMap;
delete p;

I'd wonder about any code which allocated an std::map
dynamically to begin with. I don't think the risk here is very
great.
Your idea is good, but either (1) the inheritance should be
private, or (2) myMap should contain the std::map sub-object
as a member rather than a base.

In both cases, this means a lot of brunt typing to duplicate the
interface. It might be worth it, however.
 
J

James Kanze

* Jim Langston:
Only for Outlook Express users... ;-)

Or Google groups.
It seems Outlook Express is the only commonly used newsreader
that doesn't handle digitally signed messages correctly.

For what definition of "correctly"? According to the RFC,
attachments are not allowed in news, so arguably, "correctly"
would mean not displaying or propagating the message:).
(Realistically, of course: the first rule is always "be liberal
in what you accept, and conservative in what you send". No good
newsreader would ever append an attachment, but all good
newsreaders would accept it. And allowing at least a digital
signature does seem a reasonable extension to me.)
 
A

Alf P. Steinbach

* James Kanze:
Or Google groups.


For what definition of "correctly"?

According to the RFC for signed messages, early 1990s IIRC.

According to the RFC,

Wrong RFC, and it isn't an attachment: it's a signed message.

attachments are not allowed in news, so arguably, "correctly"
would mean not displaying or propagating the message:).
Nope.


(Realistically, of course: the first rule is always "be liberal
in what you accept, and conservative in what you send". No good
newsreader would ever append an attachment, but all good
newsreaders would accept it. And allowing at least a digital
signature does seem a reasonable extension to me.)

Agreed.


Cheers, & hth.,

- Alf
 
S

Sam

Jeff said:
#include <iostream>
#include <ostream>

int main()
{
std::map<int, int> m(map_initializer(3,4)(5,6)(7,8)); ...
return 0;
}

That's a neat idea. It could probably be made a little more efficient
by replacing the calls to map::eek:perator[] with calls to map::find and

operator[] takes only one argument. You can use a std::pair, but it'll make
this even more ugly.



-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (GNU/Linux)

iD8DBQBHvWo7x9p3GYHlUOIRAnzkAJ9rd3VYOlnl6mk9f6Nk2mVwbrt4/QCbBVn+
wvGArnNFpaAb6SHW9LJnAJU=
=2p1k
-----END PGP SIGNATURE-----
 
J

Jeff Schwab

Sam said:
Jeff said:
#include <iostream>
#include <ostream>

int main()
{
std::map<int, int> m(map_initializer(3,4)(5,6)(7,8)); ...
return 0;
}

That's a neat idea. It could probably be made a little more efficient
by replacing the calls to map::eek:perator[] with calls to map::find and

operator[] takes only one argument.

Right, but what's the relevance?
You can use a std::pair, but it'll make this even more ugly.

I don't understand what you mean. I was pointing out that in:

m[k] = v;

The map first has to default-construct the value (assuming this is the
first time the map entry has been accessed), then assign to it. One
could instead check for the correct position of the value with
lower_bound (I mistakenly said map::find), then use that iterator as a
hint to insert the new value. This way, the value in the map can be
copy-constructed in the first place, rather than default-constructed,
then assigned.
 
J

Jeff Schwab

James said:
barcaroller said:
Is there a way in C++ to initialize an STL map in one
statement (the way arrays can be initialized in C)?
For example, instead of using:
map<type1,type2> mymap;
mymap[key1] = value1;
mymap[key2] = value2;
I would like to use something like:
// wrong syntax!
map<type1,type2> mymap = { (key1, value1), (key2, value2) };
There's no special syntax for maps.

I think there will be in the next version of the standard. (I
know that there was a proposal for extended initializers, but
I'm not sure what the current status of the proposal was.)

The proposal:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2215.pdf

I didn't know about that. Very neat. I'm not thrilled with g++ warning
me about:

std::tr1::array<int, 42> = { 0 }; // g++ wants { { 0 } }

It would bother me less if there were consistent syntax for primitive
and UD types.

This is the only way to create a const map.

That's a very good point, and is the reason my "not really
initialization" solutions are inferior.
int main() {
pair_type initializers[] =
{ pair_type(key1, value1), pair_type(key2, value2) };
map_type m(initializers, initializers + size(initializers));
}

I often find it worthwhile to define a special structure for
this, something along the lines of:

typedef std::map< std::string, double > Map ;
struct MapInit
{
char const* key ;
double value ;
operator Map::value_type() const
{
return Map::value_type( std::string( key ), value ) ;
}
} ;

Is the key stored as a char const* so that construction of the
initializers does not require any run-time overhead? Does MapInit count
as a POD type, and is there benefit to using POD initializers?
 
J

James Kanze

Jeff said:
James Kanze wrote:

[...]
Is the key stored as a char const* so that construction of the
initializers does not require any run-time overhead? Does
MapInit count as a POD type, and is there benefit to using POD
initializers?

In this case, the key is stored as a char const* more by habit
than anything else. The underlying reason is that doing so
allows static initialization, which in turn avoids any possible
order of initialization issues. If the map and the table are
defined in the same file (which is the case here), the order of
initialization is well defined, and there is no problem. If the
initialization table is defined in another translation unit,
however (e.g. because it is machine generated), then static
initialization means that it is still guaranteed to work;
otherwise, you need some special logic to ensure that the table
is initialized before the map. Similarly, if you expect the map
to be used from constructors of other static objects, and use
the singleton idiom to construct it, unless the initialization
table uses static initialization, you might have an order of
initialization problem.

And of course, if the table is small enough to be initialized by
hand, I'll use std::find_if on it, rather than bother creating a
map. In such cases, ensuring static initialization also avoids
any order of initialization problems, without the need of a
singleton.

(FWIW: ensuring static initialization is practically the only
time I use char[] or char* instead of std::string.)
 
J

James Kanze

Sam said:
Jeff Schwab writes:
#include <iostream>
#include <ostream>
int main()
{
std::map<int, int> m(map_initializer(3,4)(5,6)(7,8));
...
return 0;
}
That's a neat idea. It could probably be made a little more efficient
by replacing the calls to map::eek:perator[] with calls to map::find and
operator[] takes only one argument.
Right, but what's the relevance?

Any ugliness would be hidden in map_initializer, so who cares.
I don't understand what you mean. I was pointing out that in:
m[k] = v;
The map first has to default-construct the value (assuming
this is the first time the map entry has been accessed), then
assign to it.

That's not really the issue---you're going to deep copy the map
anyway, so a little more or less shouldn't matter. The issue is
really one of semantics. Using insert or operator[] have
different semantics in the case of duplicates, and in the case
of insert, you can easily check the return value, and generate
an error (exception or assertion failure) in case of duplicates,
which is probably the most useful semantics.
One could instead check for the correct position of the value
with lower_bound (I mistakenly said map::find), then use that
iterator as a hint to insert the new value.

You could, but I don't see what that buys you. Insertion
without the hint (or with the wrong hint) is O(ln n). With the
hint, it is O(1), but lower_bound is O(ln n), so there's no
point in using it just to provide a hint.
This way, the value in the map can be copy-constructed in the
first place, rather than default-constructed, then assigned.

Which could also be a very important advantage if one of the
types didn't support default construction.

All in all: forget about optimization---good design requires the
use of insert.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top