STL map stores searched keys - c++

I have just found out that when I search a map like :
std::map<std::string, int> aMap;
the keys I search start to be part of the map. In the case above the values are stored as zeros. In case of pointers it stores the values as 0 valued pointers
I am doing the search with the [] operator, like in :
int a = aMap["some key"];
Can you confirm this ? I think I have misinterpreted the [] operator. Is it doing an assignment ?!
Where can I find STL documentation of these kind of "features" ?

Are you searching it with the [] operator? If so, then yes, this is the defined behaviour.
You should use the 'find' method if you don't want this behaviour.
A good reference for the STL is the Nicolai Josuttis book.

If you look in the map using the [] operator, then, yes, you will generate default objects. If you use 'find', on the other hand, it does not. This is because the [] operator MUST return a reference to an object in the map, so it has no choice but to generate one if one is not already there.

The reasoning behind this is:
[] is defined as
T& operator[](KEY k)
A Reference can never be NULL, so some value must be returned. STL solves this by inserting a default initialized element.

int a = aMap["some key"];
Here map checks to see if the key "some key" is already exists the map:
if yes then the reference of
the value corresponding to the "Some key" is returned.
If "some key" doesnot exists in the map then the
key "Some Key" is inserted in the map with default value.
reference of the newly inserted value will be
returned.
The correct way of testing whether a key exits in map (without adding the key to map) is:
std::map<key,value>::iterator iter = myMap.find("Some Key");
if( iter != myMap.end())
{
//key exists
}
else
{
//no key
}

How do you search???
if(!aMap[key]) // not found
This is not correct, once you access a map via operator[] the apropriate place is created and the reference is returned.
You need to use
if(aMao.find(key)==aMap.end()) // not found

It sounds like you're using the bracket operator, i.e.
if (aMap["string"] == something)
Don't do that. Instead, use map::find.
The bracket operator will automatically insert the key into the map if it doesn't exist, using the default for the value part.

If by search you mean using operator[], like this:
if ( m["foo"] == 42 ) {
// found
}
else {
// not
}
then yes, that will create an entry for "foo" if it does not already exist. For this reason, you should generally avoid using operator[] for maps and use the named function sfind() and insert() instead.
As for where to find out information on this behaviour, the best book on the standard library is The C++ Standard Library by Nicolai Josuttis.

Related

Why can't I insert objects into a map in c++

So i have a function that is supposed to return a following map:
map<MyObj*, Datastruct>
While trying to insert object from a list which contains said objects:
for(auto element: myList){
Datastruct str;
str.property1 = element.property1;
str.property2 = element.property2;
saidMap.insert(element.PointerToMyObj, str);
}
Objects in myList contain a pointer to myObj and some properties i need to move from list to a map. Having executed this code in a function below:
map<MyObj*, Datastruct> listToMap = convertList(myList);
I get yelled at by the compiler that:
"no matching function to call to 'std::map::insert(MyObj*&, Datastruct&)"
Here i am helpless. I don't know why compiler would show that i am trying to pass a reference to the function if element in myList(see above) contains
MyObj *PointerToMyObj;
which, the way i see it, is a correct type to pass to the insert function right?
I also tried with
std::make_pair
whereupon compiler yells at me for trying to insert a pair into a
map<MyObj*, Datastruct>.
I am utterly lost. Could someone explain to me what i am doing wrong?
The map's value type is not
std::pair<MyObj*, Datastruct>
but
std::pair<MyObj* const, Datastruct>
Presumably you hardcoded the pair type somewhere, but missed out the const.
Or, if you wrote saidMap.insert(std::make_pair(element.PointerToMyObj, str)) then that should have worked and something else is wrong in your code.
But it's much easier to use emplace:
saidMap.emplace(element.PointerToMyObj, str);
This C++11 version of insert has all the magic machinery needed to make that work "transparently".
Your code is almost alright. You're getting this compiler error because map::insert really doesn't have such overload. It's a tiny detail you've missed on reading up when looking at the std::map documentation. It is so that map::insert doesn't work as implicit constructor, which is how you are trying to use it in the pasted code.
This is something you can achieve by emplacing - map::emplace:
for(auto element : myList){
Datastruct str;
str.property1 = element.property1;
str.property2 = element.property2;
saidMap.emplace(element.PointerToMyObj, str);
}
Using map::insert(): The insert function is used to insert the key-value pair in the map and has 3 general overloads, in fact, they are more, but these are the main ideas behind how to insert in a map.
insert(pair): simply inserts a new pair in the map, where pair.first is the key and pair.second is the value. Only happens when the key is not already in the map.
insert(it, pair): insert using an iterator and a pair, where it is a pointer to the location where you want to insert your pair at.
insert(begin, end): used for copying the elements from another map by accepting iterators to begin and end of the map.
In your case, you've missed the to actually pass a pair. You can construct it inside of the insert argument list as follows:
for(auto element : myList){
Datastruct str;
str.property1 = element.property1;
str.property2 = element.property2;
saidMap.insert( /*implicitly derive a pair as:*/ { element.PointerToMyObj, str } );
}
Note: This is a post-C++11 functionality, so make sure you're setting compiler's C++ version to at least that.
I've reproduced your code with the working version of the map::insert in Compiler Explorer (godbolt) for you have a look (in both gcc and clang compilers): https://godbolt.org/z/7Mqut0.
Hope this helps!

std::unordered_map::insert vs std::unordered_map::operator[]

I have a container of type unordered_map and I was wanting confirmation of which version I should use if I want to add an element to the map. I want it to overwrite the old value with the new presented if it exists and just add it if it does not.
I see that insert adds the element if it exits and also returns a pair of iterator and bool where the bool indicates if the insert is successful. I also see that operator[] adds the element if it does not exist and overwrites it if it does.
My question is basically if I should I be using operator[] for this purpose or are there any gotchas that I haven't considered. Also if my perception of these methods is wrong, please correct me.
here is what I was going to do. Data is a scoped enum of storage type int
void insertData(const Data _Data, const int _value)
{
int SC_val = static_cast<int>(_Data);
//sc val is now the integer value of the Data being added
//returns a pair of iterator and bool indicating whether the insert was successful
auto ret = baseData.insert(std::pair<int,int>(SC_val,_value));
if (ret.second == false)
{//if the insert was not successful(key already exists)
baseData[ret.first->first] = _value;
}
}
or should I just do
int index = static_cast<int>(_Data);
baseData[index] = _value;
I am leaning towards the operator[] version as I see no real difference and it is much less code. Please advise and thank you all in advance.
insert and operator[] are both very useful methods. They appear similar, however, the details make them very different.
operator[]
Returns a reference to the element you are searching for. When no element exists, it creates a new default element. (So requires default constructor)
When used to insert an element: myMap[key] = value;, the value will override the old value for the key.
insert
Returns an iterator and a bool. The iterator is to the element. The bool indicates if a new element was inserted (true), or it already contained an element for the key (false).
Using insert doesn't require a default constructor.
When used to insert a new element: myMap.insert({key, value});, the old value does not get updated if key already exists in the map.
insert_or_assign
Tnx to Marc Glisse who mentioned it in the comments.
This method is similar to insert. The difference is in the behavior when the element already exists, in which case it will override the existing element.
Returns an iterator and a bool. The iterator is to the element. The bool indicates if a new element was inserted (true), or it already contained an element for the key (false).
Using insert_or_assign doesn't require a default constructor.
When used to insert a new element: myMap.insert({key, value});, the old value gets updated if key already exists in the map.
Building your map
Your use-case inserts data into the map and assumes that the key doesn't exist.
Writing baseData[index] = _value; will exactly do what you want.
However, if I would have to write it, I would go with the insert variant:
auto successfulInsert = baseData.emplace(SC_val, _value).second;
assert(successfulInsert && "Value has been inserted several times.");
Just using operator [] perfectly fits for your case.
FYI: Quote from cppreference.com std::unordered_map:
std::unordered_map::operator[]
Returns a reference to the value that is mapped to a key equivalent to key, performing an insertion if such key does not already exist.
I see no real difference and it is much less code.
You're right!
It seems that you want to insert data only when it is not exist in the baseData.
You can use count() to check if the data is in the map like this:
int index = static_cast<int>(_Data);
if(!baseData.count(index))
{
baseData[index] = _value
}

Can I get a value from std::map without crashing if the key doesn't exist?

I have C++ code like this:
if(rtstructure.find(varName) != rtstructure.end()) {
rtdef = rtstructure[varName];
}
where rtstructure is std::map with std::string for the key.
This code works but it seems like a waste to make it search twice for the same key. If I omit the if case around the assignment, the program crashes if varName points to a key that doesn't exist.
Can I in a single map operation look up a key in a std::map and get its value if it exists, without crashing if it doesn't exist?
find give you a std::map<>::iterator that holds/point to std::pair<>. The iterator can be saved and reused (given that you did not do anything to invalidate it such as erase).
// i don't know the type of rtstructure so i use auto
// you can replace it to the correct type if C++11 is not available
auto it = rtstructure.find(varName);
if(it != rtstructure.end()) {
rtdef = it->second;
}

What does the STL map[key] return if the key wasn't a initialized key in the map? [duplicate]

This question already has answers here:
What happens if I read a map's value where the key does not exist?
(7 answers)
Closed 6 years ago.
Here is some example code:
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main()
{
map<char, string> myMap;
myMap['a'] = "ahh!!";
cout << myMap['a'] << endl << myMap['b'] << endl;
return 0;
}
In this case i am wondering what does myMap['b'] return?
A default constructed std::string ins inserted into the std::map with key 'b' and a reference to that is returned.
It is often useful to consult the documentation, which defines the behavior of operator[] as:
Returns a reference to the object that is associated with a particular key. If the map does not already contain such an object, operator[] inserts the default object data_type().
(The SGI STL documentation is not documentation for the C++ Standard Library, but it is still an invaluable resource as most of the behavior of the Standard Library containers is the same or very close to the behavior of the SGI STL containers.)
A default-constructed object (eg, an empty string in this case) is returned.
This is actually returned even when you say map['a'] = "ahh!!";. The [] operator inserts a default-constructed string at position 'a', and returns a reference to it, which the = operator is then called on.
If you try to access a key value using indexing operator [], then 2 things can happen :
The map contains this key. So it will return the corresponding key value
The map doesn't contain the key. In this case it will automatically add a key to the map with key value null.
As 'b' key is not in your map so it will add this key with value ""(empty string) automatically and it will print this empty string.
And here map size will increase by 1
So to look-up for a key you can use .find(), which will return map.end() if the key is not found.
And no extra key will be added automatically
And obviously you can use [] operator when you set a value for a key
std::map operator[] inserts the default constructed value type in to the map if the key provided for the lookup doesn't exist. So you will get an empty string as the result of the lookup.

STL map - insert or update

I have a map of objects and I want to update the object mapped to a key, or create a new object and insert into the map. The update is done by a different function that takes a pointer to the object (void update(MyClass *obj))
What is the best way to "insert or update" an element in a map?
The operator[]
With something like the following snippet:
std::map<Key, Value>::iterator i = amap.find(key);
if (i == amap.end())
amap.insert(std::make_pair(key, CreateFunction()));
else
UpdateFunction(&(i->second));
If you want to measure something that might improve performance you might want to use .lower_bound() to find where an entry and use that as a hint to insert in the case where you need to insert a new object.
std::map<Key, Value>::iterator i = amap.lower_bound(key);
if (i == amap.end() || i->first != key)
amap.insert(i, std::make_pair(key, CreateFunction()));
// Might need to check and decrement i.
// Only guaranteed to be amortized constant
// time if insertion is immediately after
// the hint position.
else
UpdateFunction(&(i->second));
something like:
map<int,MyClass*> mymap;
map<int,MyClass*>::iterator it;
MyClass* dummy = new MyClass();
mymap.insert(pair<int,MyClass*>(2,dummy));
it = mymap.find(2);
update(it.second);
here a nice reference link
The operator[] already does, what you want. See the reference for details.
The return value of insert is "a pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool denoting whether the insertion took place."
Therefore you can simply do
auto result = values.insert({ key, CreateFunction()});
if (!result.second)
UpdateFunction(&(result.first->second));
NOTE:
Since your question involved raw pointers, and you said you wanted your Update function to take a pointer, I have made that assumption in my snippet. Assume that CreateFunction() returns a pointer and UpdateFunction() expects a pointer.
I'd strongly advise against using raw pointers though.
In C++17, function insert_or_assign insert if not existing and update if there.