Can I rely on std::map::operator[] to touch? - c++

I have a C++ program in which I want to insert default values for any keys missing in a std::map. I'm thinking the easiest way to do this would be to use std::map::operator[]() like the POSIX touch command - that is, to leave the value unchanged if it already exists, but to create it if it doesn't. For example,
#include <map>
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<int> keys = {0, 1};
map<int, int> m;
m[1] = 5;
m[2] = 12;
for (const int i : keys)
{
m[i]; // touch value
}
for (auto const & kv : m)
{
cout << kv.first << ", " << kv.second << endl;
}
}
Can I be sure that the compiler won't optimize out the m[i]; statements, since I'm not "doing" anything with them? (Not explicitly assigning to, not reading from.)

Yes you can be sure. Optimizing the call away would change the observable behavior of your program, and the compiler is not allowed to do this (except in the case of RVO).
This is known as the as-if rule.

Yes, you can be sure. It's perhaps more intuitive when you consider that the line in question is equivalent to this:
m.operator[](i);
…and you don't expect arbitrary function calls to be optimised out of your program, if they do anything.

The [] operator does indeed default-construct the value that would be at that key's location if you do not assign it something.
Reference Link
If k does not match the key of any element in the container, the
function inserts a new element with that key and returns a reference
to its mapped value. Notice that this always increases the container
size by one, even if no mapped value is assigned to the element (the
element is constructed using its default constructor).

Related

Retrieval of value back from C++ map<T, const T&> returns same object

I created a map of type map<T, const T&>. For current example purpose, let say T is:
class Bar {
public:
Bar(int x) {this->x = x;}
int x;
};
Next I create a map and insert Bar keyed with some integers.
Bar bs[] = {Bar(1), Bar(2), Bar(3)};
map<int, const Bar&> my_map;
for (int i = 0; i < 3; i++) {
const Bar &b = bs[i];
cout << "Setting map." << i
<< " with x = " << b.x << endl ;
my_map.insert(std::make_pair(i, b));
}
So far everything looks good, and b.x prints the values 1; 2; 3 as expected. Next we retrieve these values back.
for (int i = 0; i < 3; i++) {
auto iter = my_map.find(i);
if (iter == my_map.end()) {
cout << "Not found!" << endl;
continue;
}
cout << "map." << i << " = " << iter->second.x << endl;
}
The output prints the last value each time as shown below.
// map.0 = 3
// map.1 = 3
// map.2 = 3
And that's what is confusing to me, as I expect 1; 2; 3. If I replace value type of map with just const Bar it gives 1; 2; 3. I've been trying to make sense out of it, but so far it just looks like undefined behaviour to me. The wildest explanation I can imagine is that &b is like a box storing pointer to the object, and the box ends up being shared across loop, and make_pair uses &b as a box value than like a pointer/reference (and hence explains the last value being printed).
Edit: I understand it may not be good idea to use map like this, but I'm curious why this is happening than what should I be using instead. As in semantically, what did I miss when I wrote this and why it went through compiler, or why compiler made whatever assumption it made.
Edit: Example on repl.it running the code: https://repl.it/repls/IgnorantExhaustedBluejay
Essentially the same problem as here: How can I have a pair with reference inside vector?
Your call to std::make_pair creates a temporary std::pair object that does not have a reference as its second member. The second member of the pair is a regular value of type Bar. Meanwhile, your map stores references. The reference gets bound to the second member of the temporary created by std::make_pair. Later the temporary gets destroyed. The reference becomes dangling.
Each temporary on each iteration of the cycle is apparently created at the same location in memory. So, all these dangling references in your map refer to the same location in memory. Which just happens to hold the residual value of 3 at the time of printing. That explains the output.
A map with raw references is not a very good idea. But if you want to somehow force it to work with raw references, stop using std::make_pair. Instead, manually construct a proper std::pair, making sure to explicitly specify the proper types
my_map.insert(std::pair<const int, const Bar &b>(i, b));
Or you can keep using std::make_pair as follows
my_map.insert(std::make_pair(i, std::cref(b)));
But switching entirely to std::reference_wrapper and std::cref is a better idea.
P.S. BTW, in C++17 mode GCC refuses to compile the code with raw references. C++14 mode does compile it.
I wasn't even aware that it's possible to have a map of references
You should probably simply store the object you want directly :
map<int, Bar> my_map;
If you want the "Bar"s objects to live outside the map, you should use pointers instead of references. Just be sure you don't destruct the Bar objects without removing them from the map :
map<int, Bar*> my_map;
my_map[2] = &bs[0];
and then:
int x = my_map[2]->x;
Edit
I think the map is holding a reference to the temporary pair. You can see this in debug if you extract the creation of the pair :
auto tempPair = std::make_pair(i, b);
my_map.insert(tempPair);
Then after adding bs[0] if we run the creation of the pair, the value of my_map[0] change even before adding the second one:
This makes it work:
my_map.insert(std::make_pair(i, std::reference_wrapper<const Bar>(b)));

How to make unordered_map<string*, V> hash by value instead of reference?

I have the following
The two equivalent strings bar and bartest do not map to the same value in unordered_map. How can I make this happen?
Of course they don't map to the same value, const string* is a pointer type and since you call new string twice, you end up with two separate objects that don't have memory identity (the pointers are not equal).
What's worse, you leak both of them at the end of your program.
What's (arguably) worse still, owning raw pointers and naked new calls are considered harmful in modern c++.
Luckily it's all fixed with unordered_map<string, int> - no pointers required whatsoever.
Your C++ is in fact "Java-- + C".
Remove all those silly pointers.
All you need is unordered_map<string,int> and use plain values instead of heap-allocated "news"
just do
#include <unordered_map>
#inclide <string>
#include <iostream>
int main()
{
unordered_map<string,int> mymap;
mymap["bar"] = 5;
mymap["bartest"] = 10;
std::cout << mymap["bar"] << ' ' << mymap["bartest"] << '\n';
return 0;
}

C++ map do plus plus without initialization

map<int, int> mp;
I can understand following code:
mp[1] = 1;
mp[2] = 2;
But how does this make sense?
mp[3]++;
without setting mp[3] = n; (n could be an integer).
When map's operator[] references an element that does not yet exist, it adds the element by value-initializing the entry. For int, value-initializing means initializing to zero. So mp[3]++, if the key 3 did not already exist, ends up setting the associated value to 1.
Please refer the document that managed by SGI: MAP
The mp[3]++ will automatically call mp[3] and if you see the document, you can see when mp[X] is equal to (*((m.insert(value_type(k, data_type()))).first)).second. That means data_type() is calling int().
In short, int() will be called as initial value. and int() is 0; Please refer the following code.
#include <iostream>
using namespace std;
int
main(int,char**)
{
cout << int() << endl;
return 0;
}
PS. I found the right answer and reposted it. Thanks to HisBlog

is there a cleaner way to right operator[]() for a vector? [duplicate]

If I define a pointer to an object that defines the [] operator, is there a direct way to access this operator from a pointer?
For example, in the following code I can directly access Vec's member functions (such as empty()) by using the pointer's -> operator, but if I want to access the [] operator I need to first get a reference to the object and then call the operator.
#include <vector>
int main(int argc, char *argv[])
{
std::vector<int> Vec(1,1);
std::vector<int>* VecPtr = &Vec;
if(!VecPtr->empty()) // this is fine
return (*VecPtr)[0]; // is there some sort of ->[] operator I could use?
return 0;
}
I might very well be wrong, but it looks like doing (*VecPtr).empty() is less efficient than doing VecPtr->empty(). Which is why I was looking for an alternative to (*VecPtr)[].
You could do any of the following:
#include <vector>
int main () {
std::vector<int> v(1,1);
std::vector<int>* p = &v;
p->operator[](0);
(*p)[0];
p[0][0];
}
By the way, in the particular case of std::vector, you might also choose: p->at(0), even though it has a slightly different meaning.
return VecPtr->operator[](0);
...will do the trick. But really, the (*VecPtr)[0] form looks nicer, doesn't it?
(*VecPtr)[0] is perfectly OK, but you can use the at function if you want:
VecPtr->at(0);
Keep in mind that this (unlike operator[]) will throw an std::out_of_range exception if the index is not in range.
There's another way, you can use a reference to the object:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {7};
vector<int> *p = &v;
// Reference to the vector
vector<int> &r = *p;
cout << (*p)[0] << '\n'; // Prints 7
cout << r[0] << '\n'; // Prints 7
return 0;
}
This way, r is the same as v and you can substitute all occurrences of (*p) by r.
Caveat: This will only work if you won't modify the pointer (i.e. change which object it points to).
Consider the following:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v = {7};
vector<int> *p = &v;
// Reference to the vector
vector<int> &r = *p;
cout << (*p)[0] << '\n'; // Prints 7
cout << r[0] << '\n'; // Prints 7
// Caveat: When you change p, r is still the old *p (i.e. v)
vector<int> u = {3};
p = &u; // Doesn't change who r references
//r = u; // Wrong, see below why
cout << (*p)[0] << '\n'; // Prints 3
cout << r[0] << '\n'; // Prints 7
return 0;
}
r = u; is wrong because you can't change references:
This will modify the vector referenced by r (v)
instead of referencing another vector (u).
So, again, this only works if the pointer won't change while still using the reference.
The examples need C++11 only because of vector<int> ... = {...};
You can use it as VecPrt->operator [] ( 0 ), but I'm not sure you'll find it less obscure.
It is worth noting that in C++11 std::vector has a member function 'data' that returns a pointer to the underlying array (both const and non-const versions), allowing you to write the following:
VecPtr->data()[0];
This might be an alternative to
VecPtr->at(0);
which incurs a small runtime overhead, but more importantly it's use implies you aren't checking the index for validity before calling it, which is not true in your particular example.
See std::vector::data for more details.
People are advising you to use ->at(0) because of range checking. But here is my advise (with other point of view):
NEVER use ->at(0)! It is really slower. Would you sacrifice performance just because you are lazy enough to not check range by yourself? If so, you should not be programming in C++.
I think (*VecPtr)[0] is ok.

How to insert a duplicate element into a vector?

I'm trying to insert a copy of an existing vector element to double it up. The following code worked in previous versions but fails in Visual Studio 2010.
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> test;
test.push_back(1);
test.push_back(2);
test.insert(test.begin(), test[0]);
cout << test[0] << " " << test[1] << " " << test[2] << endl;
return 0;
}
Output is -17891602 1 2, expected 1 1 2.
I've figured out why it's happening - the vector is being reallocated, and the reference becomes invalid before it's copied to the insertion point. The older Visual Studio apparently did things in a different order, thus proving that one possible outcome of undefined behavior is to work correctly and also proving that it's never something you should rely on.
I've come up with two different ways to fix this problem. One is to use reserve to make sure that no reallocation takes place:
test.reserve(test.size() + 1);
test.insert(test.begin(), test[0]);
The other is to make a copy from the reference so that there's no dependency on the reference remaining valid:
template<typename T>
T make_copy(const T & original)
{
return original;
}
test.insert(test.begin(), make_copy(test[0]));
Although both work, neither one feels like a natural solution. Is there something I'm missing?
The issue is that vector::insert takes a reference to a value as the second parameter and not a value. You don't need the template to make a copy, just use a copy constructor to create another object, which will be pass by reference. This copy remains valid even if the vector is resized.
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> test;
test.push_back(1);
test.push_back(2);
test.insert(test.begin(), int(test[0]));
cout << test[0] << " " << test[1] << " " << test[2] << endl;
return 0;
}
I believe this is defined behavior. In §23.2.3 of the 2011 C++ standard, table 100 lists sequence container requirements and there is an entry for this case. It gives the example expression
a.insert(p,t)
where a is a value of X which is a sequence container type containing elements of type T, p is a const iterator to a, and t is an lvalue or const rvalue of type X::value_type, i.e. T.
The assertion for this expression is:
Requires: T shall be CopyInsertable into X. For vector and deque, T shall also be CopyAssignable.
Effects: Inserts a copy of t before p.
The only relevant vector specific quote I could find is in §23.3.6.5 paragraph 1:
Remarks: Causes reallocation if the new size is greater than the old capacity. If no reallocation happens, all the iterators and references before the insertion point remain valid.
Although this does mention the vector being reallocated, it doesn't make an exception to the previous requirements for insert on sequence containers.
As for working around this issue, I agree with #EdChum's suggestion of just making a copy of the element and inserting that copy.