I am trying to understand the following code:
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <utility>
using namespace std;
const std::string& foo(const std::pair<std::string, std::string>& a) {
// const string& foo(const std::pair<const std::string, std::string>& a) { // *First fix*
const std::string& strfoo = a.first;
return strfoo;
}
int main() {
std::map<std::string, std::string> m1;
m1.insert(std::pair<std::string, std::string>(std::string("John"),std::string("Doe")));
std::map<std::string, std::string>::iterator k = m1.begin();
//std::pair<std::string, std::string> p1 = make_pair(k->first,k->second); // *Second Fix*
const std::string& f = foo(*k);
std::cout << "Hello " << f << " Bye";
return 0;
}
If I run this code (without uncommenting anything. I am using Visual Studio), I get the output as Hello Bye
Now I have added 2 comments in the code labelled as first fix and the second fix respectively.
Both of these so called fixes are printing the output as Hello John Bye
First fix: If I comment the line
const string& foo(const std::pair<std::string, std::string>& a) {
and uncomment the first fix code which is making the first std::string as const, It prints the needed "Hello John Bye".
Second fix: If I keep everything intact, and uncomment the second fix where I am creating a copy of the element of the map. It prints the required "Hello John Bye" when passing the pair p1 instead of dereferencing the iterator.
Third fix: If I remove the reference &(ampersand) from the line
const string& foo(const std::pair<std::string, std::string>& a) {
OR
const std::string& f = foo(*k);
It also results in a copy and I get the output "Hello John Bye".
For the first one, it looks like since we are passing element of a map so we should keep the functions signature so that the key of the map should be const (I am still not sure why the code runs though)
Could someone elaborate how these fixes are working?
CodePudding user response:
map's value type is indeed std::pair<const std::string, std::string>
and not std::pair<std::string, std::string>
, but the latter can be converted in the former.
Applying foo
on temporary returns pointer which becomes dangling at end of full expression.
1.
Changing foo
signature to take exact value type avoid the creation of the problematic temporary.
-
std::pair<std::string, std::string> p1 = make_pair(k->first,k->second); const std::string& f = foo(p1);
You no longer have temporary neither.
-
string foo(const std::pair<std::string, std::string>& a); const std::string& f = foo(*k);
You no longer return reference, but a temporary.
That temporary has its life extended by the bounding to const reference f
.
std::string f = foo(*k);
reference return by foo
is used to construct f
before becoming dangling.
so your are ok too.
CodePudding user response:
Case 1: Without any fixes/modifications
First note that for a map std::map<K,V>
, std::map<K, V>::value_type
is std::pair<const K, V>
.
Second dereferencing an iterator from a std::map<K, V>
will give you a std::pair<const K, V>&
.
Now lets apply the above two facts to your example. In particular, the expression *k
gives us
std::pair<const std::string, std::string>&
Next, the function foo
in this case takes a
const std::pair<std::string, std::string>& a
Note that here the std::pair
itself is const
. So this means, its members are also const since the members of a const
object are itself(themselves) const
.
Also, this function foo
returns a reference to const string. So you should get Hello John Bye
as output instead of Hello Bye
. My guess is that this is a bug in MSVC since in gcc and clang you get the expected(Hello John Bye
) as output.
Case 2: With first fix
In this first fix, you have change your function parameter to be
const std::pair<const std::string, std::string>& a
In the above statement, you have explicitly set the first
member of the std::pair
as const
which again should give the output Hello John Bye
. In the previous case(CASE 1), the first
member was implicitly const
. So Case 1 and Case 2 are not really so different except that in Case 1 the second
member of the passed pair is also const
. You can verify this by trying to change the second
member by adding the following inside foo
's body in Case 1
//std::string &secondStr = a.second; //this won't work in CASE 1 because second member is implicitly const in Case 1
const std::string &secondStr = a.second; //this will work in CASE 1
Case 3: With second fix
std::pair<std::string, std::string> p1 = make_pair(k->first,k->second);
const std::string& f = foo(p1);
This time you're passing the std::pair
, p1
that you created using make_pair
. Again, just like Case 1, here the parameter of the function foo
is const
so the first
and second
members of the parameterwill be implicitly const
and the output should be Hello John Buy
.
Case 4: With third fix
string foo(const std::pair<std::string, std::string>& a);
const std::string& f = foo(*k); //f extends the lifetime of the temporary returned by foo
In this case, there is no change in the function parameter but now we're returning the string by value. Here, f
extends the lifetime of the temporary returned by the call to foo
and the output should be the same as before(Hello John Bye
).
Summary
The output Hello Bye
that you're getting in the very first case seems to be a bug in MSVC to me.