2

In a codebase that I am working on I am seeing a lot of things like:

std::map<int,int> m;
// ...some code...
const auto& p = m.find(10);

with reference in the type of p. The map's find returns an iterator and not a reference to one, so what is the point of writing such code and how does it work - does it call an iterators copy ctor on the returned value, as it can not just reference the temporary that is being returned, right?

This is just one example, while there are many similar cases throughout that codebase when the functions are returning by value but the caller uses

type& name = func_call()

is there some benefit to doing this that I am missing?

JeJo
  • 30,635
  • 6
  • 49
  • 88
Pukki
  • 103
  • 6

3 Answers3

2

In the particular case you show; there is no benefit in using a const reference for p. You should not write code like this, in particular since iterators are meant to be copied. The habit might come from consuming functions that return const-qualified references to heavy, not-to-be-copied objects.

But how does it even work? const-qualified references extend the lifetimes of the object they refer to - under certain circumstances (see e.g. here for more info). And those apply in your snippet, i.e., the const auto& makes sure that the object returned by m.find is alive in the scope of p.

Whether you write const auto& p = m.find(10); or const auto p = m.find(10) hence makes no difference for how the code works. The assembly is probably identical, too. Not only is there return value optimization (which erases any copying of iterators), there is also the need of holding the object p refers to in the stack frame.

Last, let me point to Abseil TotW #107, which is a good read on lifetime extensions. From there:

you probably shouldn’t rely on lifetime extension in the explicit case of reference-initialization: it’s not gaining you much/any performance, and it is subtle, fragile, and prone to cause extra work for your reviewers and future maintainers.

lubgr
  • 37,368
  • 3
  • 66
  • 117
2

You can extend the lifetime of the temporary using const-ref (i.e. const lvalue reference).

The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11)

Also good to read: Why do const references extend the lifetime of rvalues?

This means the

const auto& p = m.find(10);

is a valid statement. No undefined behaviour here.

On the other side, it has no benefits of having a const& to const_iterator here, which is would have been cheaper to copy simply.

/* const */ auto p = m.find(10);

In the second case

type& name = func_call()

you can not extend the lifetime of temporary by a reference. Therefore the above code (i.e. when you use name latter in the code), invokes undefined behaviour.

More read: Is the practice of returning a C++ reference variable evil?

JeJo
  • 30,635
  • 6
  • 49
  • 88
1

Iterators can be used like pointers whereas *iterator accesses any value of the data. My thought looks like this snippet of code.

std::multiset<T> m;
typename std::multiset<T>::const_iterator pos;
pos= m.begin();

access is done via *pos;

for(pos= m.begin(); !(pos==m.end()); ++pos)
    *pos= *pos+*pos;