The range initialization line of a for(:)
loop does not extend lifetime of anything but the final temporary (if any). Any other temporaries are discarded prior to the for(:)
loop executing.
Now, do not despair; there is an easy fix to this problem. But first a walk through of what is going wrong.
The code for(auto x:exp){ /* code */ }
expands to, basically:
{
auto&& __range=exp;
auto __it=std::begin(__range);
auto __end=std::end(__range);
for(; __it!=__end;++__it){
auto x=*__it;
/* code */
}
}
(With a modest lies on the __it
and __end
lines, and all variables starting with __
have no visible name. Also I am showing C++17 version, because I believe in a better world, and the differences do not matter here.)
Your exp
creates a temporary object, then returns a reference to within it. The temporary dies after that line, so you have a dangling reference in the rest of the code.
Fixing it is relatively easy. To fix it:
std::string const& func() const& // notice &
{
return m.find("key")->second;
}
std::string func() && // notice &&
{
return std::move(m.find("key")->second);
}
do rvalue overloads and return moved-into values by value when consuming temporaries instead of returning references into them.
Then the
auto&& __range=exp;
line does reference lifetime extension on the by-value returned string
, and no more dangling references.
As a general rule, never return a range by reference to a parameter that could be an rvalue.
Appendix: Wait, &&
and const&
after methods? rvalue references to *this
?
C++11 added rvalue references. But the this
or self parameter to functions is special. To select which overload of a method based on the rvalue/lvalue-ness of the object being invoked, you can use &
or &&
after the end of the method.
This works much like the type of a parameter to a function. &&
after the method states that the method should be called only on non-const rvalues; const&
means it should be called for constant lvalues. Things that don't exactly match follow the usual precidence rules.
When you have a method that returns a reference into an object, make sure you catch temporaries with a &&
overload and either don't return a reference in those cases (return a value), or =delete
the method.
S().func()
This constructs a temporary object, and invokes a method that returns a reference to a std::string
that's owned (indirectly) by the temporary object (the std::string
is in the container that's a part of the temporary object).
After obtaining the reference, the temporary object gets destroyed. This also destroys the std::string
that was owned (indirectly) by the temporary object.
After that point, any further usage of the referenced object becomes undefined behavior. Such as iterating over its contents.
This is a very common pitfall, when it comes to using range iteration. Yours truly is also guilty of getting tripped over this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With