Consider the paradigmatic max template function, std::max():
// From the STL
// TEMPLATE FUNCTION _Debug_lt
template<class _Ty1, class _Ty2> inline
bool _Debug_lt(const _Ty1& _Left, const _Ty2& _Right,
_Dbfile_t _File, _Dbline_t _Line)
{ // test if _Left < _Right and operator< is strict weak ordering
if (!(_Left < _Right))
return (false);
else if (_Right < _Left)
_DEBUG_ERROR2("invalid operator<", _File, _Line);
return (true);
}
// intermediate #defines/templates skipped
// TEMPLATE FUNCTION max
template<class _Ty> inline
const _Ty& (max)(const _Ty& _Left, const _Ty& _Right)
{ // return larger of _Left and _Right
return (_DEBUG_LT(_Left, _Right) ? _Right : _Left);
}
… Or, in simpler form:
template<typename T> inline
T const & max(T const & lhs, T const & rhs)
{
return lhs < rhs ? rhs : lhs;
}
I understand why the max template must return by reference (to avoid expensive copies and to avoid the requirement for a copy constructor).
However, doesn’t this lead to the possibility of dangling references, as in the following code?
int main()
{
const int & max_ = ::max(3, 4);
int m = max_; // Is max_ a dangling reference?
}
In this case, the code builds and runs fine (VS 2010) and the value m is set to 4. However, it strikes me that max_ is a dangling reference since the hard-coded rvalues 3 and 4 were passed directly to max, and the storage allocated for these hard-coded rvalue constants can rightfully be de-allocated by the time the following line of code is reached.
I could envision analogous edge-cases for full-fledged objects, such as
class A
{
friend bool operator<(A const &, A const &)
{
return false;
}
};
int main()
{
const A & a_ = ::max(A(), A());
A a = a_; // Is a_ a dangling reference?
}
Am I correct that this usage of max – in which rvalues defined within the call argument list are passed as arguments – is an example of a potential “gotcha” with the use of max, unavoidable since max needs to be defined to return a reference in order to avoid expensive copies (and to avoid the requirement of a copy constructor)?
Might there be real-life circumstances in which it would be good programming practice to define a version of max that returns its result by value, rather than by reference, for the reason discussed in this question?
Yes, it’s a dangling reference.
Yes, it’s a gotcha.
A version that returns by value might be useful provided that you use it by default, and switch to by-reference in the cases where you need it. If you use the by-reference one by default and only switch to the by-value when you need it then you’ll still fall foul of the gotcha, because realizing that you need it is the same thing as realizing you should have written
const A a_= ::max(A(), A());.Unfortunately the by-value one introduces a new gotcha:
(Actually, looking at this code I reckon if you call it
max_by_valthen you won’t write this).You could return by const value, and some people used to recommend that operator overloads should return by const value. But that introduces a performance gotcha in C++11:
and even in C++03 it prevents the equivalent explicit optimization
swap(c, constmymax(a,b)).