Understanding std::pair <const T&, const U&> - c++

I am trying to create a class that instantiates an std::pair<const T&, const U&> from two member variables. I started this out of curiosity and was surprised to see the results from creating multiple objects of, in the example below, Pair.
struct Pair {
int num;
string s;
pair<const int&, const string&> pr{make_pair(num, s)};
Pair(int n, const string& s) : num(n), s(s) {}
};
ostream& operator<<(ostream& out, const Pair& p) {
out << setw(2) << p.num << ", " << p.s << ": <" << p.pr.first << "," << p.pr.second << ">";
return out;
}
/******************************************************************************/
int main() {
cout << string(15, '-') << " PIECEMEAL " << string(15, '-') << endl;
Pair p1{1, "normal1"};
Pair p2{2, "normal2"};
cout << p1 << endl;
cout << p2 << endl << endl;
cout << string(15, '-') << " VECTOR " << string(15, '-') << endl;
vector<Pair> v;
for (int i=0; i< 05; ++i) {
v.emplace_back(i, "from_vec'" + ::to_string(i) + "'");
}
for(const auto & p : v) {
cout << p << endl;
}
}
I thought that the const reference type declarations within the pair would refer to the Pair members, num and s, respectively. However, when I create multiple Pairs, it appears that the references within the pair end up referencing (to some degree) the Pair members of the most recently constructed Pair.
Here is the output:
--------------- PIECEMEAL ---------------
1, normal1: <2,normal2>
2, normal2: <1586964272,normal2>
--------------- VECTOR ---------------
0, from_vec'0': <4,from_vec'4'>
1, from_vec'1': <1586964272,from_vec'4'>
2, from_vec'2': <1586964272,from_vec'4'>
3, from_vec'3': <1586964272,from_vec'4'>
4, from_vec'4': <1586964272,from_vec'4'>
I looked at cppreference in an attempt to understand what was going on, but still not grasping it.
Why do the references within the pair no longer reference the Pair members, num and s?

There are 2 problems with your code.
First:
pair<const int&, const string&> pr{make_pair(num, s)}
std::make_pair(num, s) first create a temporary pair<int, string>. Then pr will be initialized with this temporary pair.
Note that pair<int, string> is not the same as pari<const int&, const string&>, which means it will not trigger the default move constructor.
Instead, if you looked at cppreference, this would trigger the 6th overload, and essentially assign pr.first and pr.second with this temporary pair, hence dangling reference.
To fix it, you should specifically create a pair of references with:
pr{std::make_pair(std::ref(num), std::ref(s)}
Or just create pr directly with num and s:
pr{num, s}
Second:
v.emplace_back(i, "from_vec'" + ::to_string(i) + "'");
This line would trigger the default move constructor, since you didn't specify one yourself, which would perform a member-wise move on Pair.
However, what happens when you move pr to the new Pair? The old pr was referencing the old num and old s. Moving the old pr to the new Pair doesn't change the value of pr, hence it will continue referencing the old num and old s, hence dangling reference.
So instead of relying on default generated move constructors, you must define them manually:
Pair(Pair&& pair) noexcept
: num(std::exchange(pair.num, {})
, s(std::move(pair.s))
{}
Note, you don't need to construct pr within initializer list since you already have a default initializer for pr.
Demo

You are inadvertently creating a pair that holds const references to another pair that was allocated on the stack and immediately freed. In other words, undefined behavior.
Step by step, here's what's happening when you instantiate a Pair:
The Pair constructor is called.
The Pair initializer is called.
The initializer calls make_pair, creating a pair of int and std::string on the stack.
This temporary pair gets assigned to pr, and the const references inside it now refer to the memory of the temporary pair.
The initializer exits, freeing the temporary pair on the stack. the int and string references inside pr now refer to memory that has been freed.
What's more, you really shouldn't be storing references to other members inside your struct, for memory safety reasons. If you want a std::pair of your two values, you should make a member function that returns it instead of storing it inside the struct itself.

Related

Why move on std::map is failing to move a element from one map to other

I am trying to practice how to move one map element to other - hence I tried below code:
using namespace std;
int main(void)
{
/* Initializer_list constructor */
map<char, int> m =
{
{'a', 1},
{'b', 2},
{'c', 3},
{'d', 4},
{'e', 5},
};
cout << "Move element from one map to another" << endl;
/*
char temp;
temp = map1[key1];
map2[key1]=temp;
map1.erase(key1)
*/
string a = "hello";
string b = move(a);
cout << "a=" << a << " b=" << b << endl; // here string **a** is NULL as value is moved
auto s = move(m['a']);
cout << "s=" << s << " m=" << m['a'] << endl; // here
}
Output :
Move element from one map to another
a= b=hello
s=1 m=1
Why move operation is failing for std::map STL container - I was expecting that after m['a'] would be empty?
Nowhere does your code remove an object from the map. That would require an operation on the map, not an object in it. No operation on an object in a map would remove that object from the map.
The operation you perform on the object in the map is that you move its value from it. The state of an object after it has been moved from is legal but indeterminate.
So the object in the map is in a valid but indeterminate state.
Why move operation is failing for std::map STL container - I was expecting that after m['a'] would be empty?
That expectation is ill-founded.
The statement
auto s = move(m['a']);
leaves m['a'] as is for couple of reasons.
That statement can be thought of as:
int&& s = move(m['a']);
I.e it just captures reference to the object in the map. It does not actually move it.
A move operation on an int doesn't alter the object being moved. Move semantics are significant only by move constructors of classes. For built-in types, it's a noop. Even for classes that don't actually acquire any resource beyond the member variables, there isn't much a move constructor can do.
Also, There is no such thing as an empty int. The notion of empty-ness makes sense only for containers.

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)));

C++ / How does a iterator work for a Set of class object?

I've been teaching myself C++, and found an interesting thing.
According to this web page,:
The most obvious form of iterator is a pointer.
So, I used to think that an iterator object works in almost the same way as a pointer (e.g. int *i);
However, now I guess that it would be more accurate to say an iterator object is like a pointer of a pointer (such as int **i) rather than a pointer (int *i).
I noticed this, when I was coding the following one.
set<Point*, Point> points; //This class encapsulates x and y coordinates for a 2 dimensional point
set<Point*, Point>::iterator it;
int x = 22;
int y = 4;
Point* pt = new Point(x, y);
points.insert(pt);
//Similar statements are here, to insert other Point objects with different arguments
for (it = points.begin(); it != points.end(); it++) {
cout << "**it: " << **it << ", *it :" << *it << ", Address : " << &it << endl;
}
As a result,
**it showed values of Pointer class objects
*it showed addresses
&it showed the address of the it object
So, is it right to say that an iterator object (it) is basically the same as **it?
Another question which is a bit different from iterators:
If I need to make a set of class object, like: set<ClassName*, ClassName> SetName;, what would be the correct way? (as long as this class contains comparable data type, like int or double)
I'd appreciate if you'd give some advice.
It is a wrong assumption. In your example the value_type of the set is pointer Point *. And *it gives you an element in the set that is a reference to some object of this value_type that is of type Point * and ++it "points" to the next element in the set. So the iterator behaves the same way as pointer to an object of value_type.
When you use expression like **it then the second dereference is not applied to the iterator. It is applied to an object in the set. You can imagine expression **it the following way
Point *pp = *it;
Point p = *pp;
Iterator of a container is responsible to provide you access to elements of the container. Thus if elements in the container has type value_type then the iterator provides access to these elements of this value_type. The elements in turn can be pointers to objects or even pointers to pointers to objects and so on.
As for your second question then if the class has the corresponding operator function and the class itself has some simple default constructor then you may use such an approach though it would be better to define a separatw comparator or simply to define operator < for objects of the class. In the last case you can write
std::set<Point> s;
without explicit second template argument.
Here is demonstrative program of your approach
#include <iostream>
#include <set>
int main()
{
struct Point
{
int x, y;
bool operator ()( const Point &p1, const Point &p2 ) const
{
return p1.x < p2.x && p1.y < p2.y;
}
};
std::set<Point, Point> s;
s.insert( { 2, 2 } );
s.insert( { 1, 1 } );
for ( const Point &p : s ) std::cout << p.x << ' ' << p.y << std::endl;
return 0;
}
The program output is
1 1
2 2

C++ assign dereference pointer object to a variable and use that

Hea everyone!
First of all, I am a completely new to C++ coming from a basic C background so it might be a little weird why I ask this.
The use scenario is that I want to change a map inside a different function by passing the map as a pointer to that function. Because I was reusing someone else's code, it was easier to assign the dereference to a variable instead of changing all of the references. This results in a similar case as this:
using namespace std;
typedef map<long, double> tl_t;
void fillmap(tl_t* m_p) {
tl_t m = *m_p;
m.insert(pair<long, double>(4, 3.0));
}
int main(int argc, char** argv) {
tl_t m;
cout << "Size: " << m.size() << "\n";
fillmap(&m);
cout << "Size: " << m.size() << "\n";
return 0;
}
The funny thing is that now both Size: strings return 0 and the original map m in the main function does not seem to be changed. This, however, works:
using namespace std;
typedef map<long, double> tl_t;
void fillmap(tl_t* m) {
(*m).insert(pair<long, double>(4, 3.0));
}
int main(int argc, char** argv) {
tl_t m;
cout << "Size: " << m.size() << "\n";
fillmap(&m);
cout << "Size: " << m.size() << "\n";
return 0;
}
As far as I can tell, these 2 cases should be working the same as both the reference of m in the main and fillmap function reference the same object. Of course the two m variables reside somewhere differently but should be referencing the same object.
As I am writing this, one thing that might be the problem is that variable m in main IS the map object while the variable m in fillmap TRIES to be the map object but can't because dereferencing the m_p pointer and assigning it to that last m doesn't actually make the last m a reference to the same object but actually copies it. Am I on the right track here?
And yes, I do know in normal use cases you should use a parameter reference in a similar situation, but this bugged the hell out of me :P.
Hopefully someone can enlighten me!
In C++ you have to add & to explicitly say that variable is reference.
In your example:
tl_t& m = *m_p;
should help.
If you use just "tl_t" you create local copy of the object which is destroyed once you leave fillmap function.
Example:
struct X {
int a,b;
}
Now types:
X - place in memory containing both a and b value.
X& - place in memory containing reference (const pointer) to the X.
X* - place in memory containing pointer to the X.
In both X and X& you can access fields of class using dot (xobject.a, xobject.b) but these are not same types.
It is because that
tl_t m = *m_p;
m will be construct by call the copy constructor, the copy process is by value. You just insert the pair<long, double>(4, 3.0) into m, not m_p
However,
(*m).insert(pair<long, double>(4, 3.0))
*m is the object you passed in by pointer, so, the pair<long, double>(4, 3.0) is inserted to *m itself.

C++ STL Vector Iterator accessing members of an Object

I think I've declared a Vector with an object correctly. But, I don't know how to access it's members when looping with Iterator.
In my code, the line --->> cout << " " << *Iter;
How do I print the contents of the members? Like *Iter.m_PackLine ???
Not sure if I used the correct terminology, but appreciate the help! Thanks
class CFileInfo
{
public:
std::string m_PackLine;
std::string m_FileDateTime;
int m_NumDownloads;
};
void main()
{
CFileInfo packInfo;
vector<CFileInfo, CFileInfo&> unsortedFiles;
vector<CFileInfo, CFileInfo&>::iterator Iter;
packInfo.m_PackLine = "Sample Line 1";
packInfo.m_FileDateTime = "06/22/2008 04:34";
packInfo.m_NumDownloads = 0;
unsortedFiles.push_back(packInfo);
packInfo.m_PackLine = "Sample Line 2";
packInfo.m_FileDateTime = "12/05/2007 14:54";
packInfo.m_NumDownloads = 1;
unsortedFiles.push_back(packInfo);
for (Iter = unsortedFiles.begin(); Iter != unsortedFiles.end(); Iter++ )
{
cout << " " << *Iter; // !!! THIS IS WHERE I GET STUMPED
// How do I output values of the object members?
}
} // end main
cout << " " << *Iter;
will only work if CFileInfo has an overloaded operator<< that can output your struct. You can output individual members of the struct instead like this:
cout << " " << Iter->m_PackLine;
Alternatively, the following is equivalent to that:
cout << " " << (*Iter).m_PackLine;
You have to put parentheses around *Iter, since the member-access operator binds thighter otherwise.
On a side-node, make your main function return int instead of void. making it return void is not valid in C++.
You declare the vector like this:
vector<CFileInfo, CFileInfo&> unsortedFiles;
The second argument to vector should be another thing. It's not needed for your code to give the vector a second argument at all. Just use this:
vector<CFileInfo> unsortedFiles;
Another thing i noticed is you increment the iterator using Iter++ (called postfix increment). For iterators, always prefer ++Iter, which is called prefix increment.
Use (*iter).member or iter->member.
You can also use temporaries:
CFileInfo &fileInfo = *iter;
cout << " " << fileInfo.myMember;
Also, for what you're doing, you'd probably want a const_iterator instead of an (mutable) iterator.
In addition, std::vector is a template accepting a typename and an allocator, not two typenames. You can use the default allocator by stripping the second template argument:
vector<CFileInfo> unsortedFiles;
vector<CFileInfo>::iterator Iter;
Some nit-picking:
main should return an int.
It'd probably be best to declare your iterator variable in the for statement.
It'd probably be faster in run-time performance to use the prefix ++ operator (++iter) instead of the postfix operator (iter++) in your for loop.
No need for your comment about main() ending.
This is the first problem I noticed:
std::vector is a template.
You have:
vector unsortedFiles;
you need something like:
vector<CFileInfo> unsortedFiles;
Now that I think about it, your template definition may have just gotten parsed out by the stackoverflow comment system.
iter->m_PackLine
or
(*iter).m_PackLine
First correct you'r vector declaration:
vector<CFileInfo > unsortedFiles;
Next you need to define an output operator for your class:
std::ostream& operator<<(std::ostream& str,CFileInfo const& data)
{
// Do something here
/* Potentailly you could do this
* But this requires that this function be a friend of the class
str << data.m_PackLine << ":"
<< data.m_FileDateTime << ":"
<< data.m_NumDownloads << ":";
* Or you could do this
data.print(str); // Make print a public const method.
*/
return str;
}
Usually you either make the output operator a friend of your class or provide a public print method that takes a stream. Either way you can then access the members and stream them manually to the output.
Once you have the output iterator defined you can change your loop to use the standard library versions:
std::for_each(unsortedFiles.begin()
unsortedFiles.end()
std::ostream_iterator<CFileInfo>(std::cout," ")
);
Thanks all, wish I could grant multiple points for the answers :)
litb also pointed out a problem I was having in my declaration of the vector. I removed the second argument in the vector declaration and it worked.
Stackoverflow parsed out some of my code, I'll be more careful in posting next time.
vector<CFileInfo, CFileInfo&> will not work at all. The second parameter to vector is the allocator the vector uses, and CFileInfo does not meet those requirements, nor does any reference type. I think you just want vector<CFileInfo>, the iterators and members will return CFileInfo& automatically.