Introduction
Say there is a Container
class which stores Widget
objects.
There is an iterator class in charge of navigating such container. This iterator class (MyIterator
) takes a const-reference to a vector of Stuff
in its constructor, which it needs to iterate over the right elements in the container. The code may look like this:
class MyIterator
{
public:
MyIterator(
const Container& container, // Container to be navigated
const std::vector<Stuff>& vec) // Parameter which controls the behaviour of the iteration
: container(container)
, stuffVector(vec)
{}
...
private:
const Container& container;
const std::vector<Stuff>& stuffVector;
};
The problem
I'm curious about the binding of rvalues to const references, and how the lifetime extensions work.
While vec
can be bound to a temporary rvalue, that will cause stuffVector
to be a dangling reference once the rvalue's lifetime is over. So code like this, aimed to create an iterator for object myContainer
is wrong:
MyIterator it = {myContainer, {stuff1, stuff2, stuff3}};
// Here it.stuffVector is a dangling reference!
Reading up on lifetime extension of temporary objects in cppreference I found:
Whenever a reference is bound to a temporary object or to a subobject thereof, the lifetime of the temporary object is extended to match the lifetime of the reference (...) There are following exceptions to this lifetime rule: (...)
- a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference.
The question
The highlighted part leads me to this question: would this piece of code succeed in getting a filtered version of the contents of myContainer
?
std::vector<Widget> filteredWidgets;
std::copy_if (
MyIterator{myContainer, {stuff1, stuff2, stuff3}},
MyContainer.end(),
std::back_inserter(filteredWidgets),
[&](Widget w) { return ...; });
The second argument is an rvalue, so there is the danger of creating a dangling reference, but my understanding of the documentation is that the rvalue's lifetime will be extended untill the end of std::copy_if
, so it would be fine. Which one is it?
NOTE: I am aware the problem and the design, as stated, might seem a bit strange, but it's inspired in real code I've found in the wild. I'm interested in the limits of the lifetime extension, not so much in coming up with different designs which would make the question irrelevant.
CodePudding user response:
Yes, the whole std::copy_if
call is the full-expression and the temporary std::vector<Stuff>
will be destroyed only after the call returns.
This is different from by-value function parameters. If the constructor took a std::vector<Stuff>
instead of a const std::vector<Stuff>&
, then it would be implementation-defined whether the object lives that long or is destroyed immediately when the constructor returns.
A full-expression is one of the expressions listed in [intro.execution]/5. None of the specific cases (such as unevaluated operands, immediate invocations, etc.) apply here and so falling through, the relevant condition is:
an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.
The copy_if
call expression is the only one in that statement to which this applies (outside of the lambda body).