This is follow up of question Is there a sequence point between return and expression in return statement? . The answer-er isn't replying to my comments , neither I'm unable to understand from his answer, nor I know how to bump the thread. So I created this question, my sincere apologies for doing this.
Consider the below code :
#include <iostream>
using namespace std;
struct foo{const char* bar ; foo(): bar("This is foo"){} };
foo returnByValue(){ return foo(); }
const foo& returnByConstRef() { return returnByValue(); } // *
int main() {
std::cout<< returnByConstRef().bar <<std::endl; // is life of temp is extended in while this expression?
return 0;
}
My understanding is that returnByValue() expression (inside returnByConstRef()) is the copy of temporary object foo() (using copy ctor) . Now returnByConstRef() which is const reference to temp object returned by returnByValue() (copy of original temp() object in the code) , Now when I invoke returnByConstRef().bar why is it undefined behavior ?
Where is my thinking wrong? , does RVO does this?
This isn't due to RVO, the standard specifies that temporaries bound to returned values do not have their lifetime extended:
N3337 [class.temporary]/5: [...] The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
[...]
The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not
extended; the temporary is destroyed at the end of the full-expression in the return statement.
[...]
For your example:
const foo& returnByConstRef()
{
return returnByValue();
}
returnByValue() is a temporary which is being bound to the return value, kind of like this:
const foo& returnByConstRef()
{
const foo& _temporary_object = returnByValue();
return _temporary_object;
//object referenced by _temporary_object is destroyed
}
Such temporaries do not have their lifetime extended; they are destroyed on function exit. Maybe it would help you to think of it like this:
function returnByConstRef
call returnByValue, put value on the stack
put pointer to value in return register
clear up stack, invalidating pointer to the value
Related
Why does the function foo gives the warning "returning reference to temporary"
const bool& foo()
{
return true;
}
if declaring bar like this is fine and doesn't generate any kind of warning
const bool& bar = true;
PS: i'm using GCC
The 2nd one is fine because the lifetime of temporary bool constructed from true will be extended to the lifetime of the reference bar.
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
But the lifetime rule doesn't work for the 1st case.
There are following exceptions to this lifetime rule:
a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of
the return expression. Such return statement always returns a dangling
reference.
So foo always returns a dangling reference.
songyuanyao answered with what the standard says about it. But that doesn't really explain why they decided c++ should behave this way.
It might be easier to think about the code if you think about what the compiler makes of it:
const bool& bar = true;
The lifetime of the temporary is extended to the lifetime of bar so this code is equivalent to:
const bool temp = true;
const bool& bar = temp;
But lets apply the same to the function return:
const bool& foo()
{
return true;
}
becomes
const bool& foo()
{
const boot temp = true
return temp;
}
Does this make it clearer that you are now returning a reference to an object that gets destroyed? The lifetime of temp can't be extended past the end of the function because 1) the storage location is in the stack frame of fooand that goes away, 2) the caller might have no idea the function returns something with extended lifetime it would have to destroy later, it might only see the function declaration const bool& foo().
So if the compiler could extend the lifetime past the end of the function then it would leak memory.
Since we can pass rvalue to function taking const left ref,
void taking(const string& ref) {}
taking("abc");
can we return rvalue as const left ref without reporting warning?
const string& returning()
{
static string s = "abc";
if (1)
{
return s;
}
else
{
return "xyz"; // warning: return-local-addr
}
}
cout<<returning()<<endl;
The issue is not the value category (lvalue vs rvalue), but the fact that you return a reference at all.
It doesn't matter what kind of reference. When the function returns, the object that the return value is referencing, will be gone. In the case of return s; that is the local variable and in the case of return "xyz"; it is the temporary constructed in the return statement (which must be constructed because "xyz" itself is not a string and so cannot be bound to const string&).
You must return-by-value:
string returning()
If you do so, then an expression of the form returning() will be a prvalue (a kind of rvalue) and can be used e.g. in a function expecting a const lvalue reference, which can also bind to rvalues.
taking(returning()) // ok!
This is ok, because the return value of the function itself is not destroyed when the function exits (that would be nonsensical). Instead it lives until the end of the full expression in which the function was called.
There are also no unnecessary copies of the object being made due to copy elision rules (except for an unlikely extra move operation in return s;).
C++11 question
Trying to understand an issue I came across in our code. Not looking for the "right" way to do this, just want to know how this is supposed to work so I can figure out how to fix things in the future
I think function f1() is fine returning a reference to the temp implicitly constructed on the same line as the p1
But what about p2? The temp is constructed implicitly in the body of f2() when calling f1(), but f2() is returning the reference that is being returned by f1(). I thought the lifetime of the temp is extended to match the lifetime of the reference
asking because in one of our compilers p2 is garbage on the next time, but on the others it is not
struct PP
{
int i;
PP(int i_) : i(i_) {}
};
const PP &f1(const PP &p)
{
return p;
}
const PP &f2(int i)
{
return f1(i);
} // does the temp live here after return?
int main()
{
const PP &p1 = f1(1);
const PP &p2 = f2(2); // is p2 valid on the NEXT line
return 0;
}
I think this is undefined behaviour, and the fact that different compilers have different outputs would suggest that.
In f2, you're returning a reference constructed by f1, which itself has a reference to the local object i. At the end of the scope, i dissapears and invalidates the reference.
In fact clang-tidy detects this godbolt example
Also in reference initialization of cppreference it states, as an exception to lifetime extension:
a temporary bound to a return value of a function in a return
statement is not extended: it is destroyed immediately at the end of
the return expression. Such return statement always returns a dangling
reference.
In an article about reference initialization at cppreference.com (Lifetime of a temporary), it says:
a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
This excerpt addresses the exceptions of extending the lifetime of a temporary by binding a reference to it. What do they actually mean by that? I've thought about something like
#include <iostream>
int&& func()
{
return 42;
}
int main()
{
int&& foo = func();
std::cout << foo << std::endl;
return 0;
}
So foo should be referencing the temporary 42. According to the excerpt, this should be a dangling reference - but this prints 42 instead of some random value, so it works perfectly fine.
I'm sure I'm getting something wrong here, and would appreciate if somebody could resolve my confusion.
Your example is very good, but your compiler is not.
A temporary is often a literal value, a function return value, but also an object passed to a function using the syntax "class_name(constructor_arguments)". For example, before lambda expressions were introduced to C++, to sort things one would define some struct X with an overloaded operator() and then make a call like this:
std::sort(v.begin(), v.end(), X());
In this case you expect that the lifetime of the temporary constructed with X() will end on the semicolon that ends the instruction.
If you call a function that expects a const reference, say, void f(const int & n), with a temporery, e.g. f(2), the compiler creates a temporary int, initailses it with 2, and passes a reference to this temporary to the function. You expect this temporary to end its life with the semicolon in f(2);.
Now consider this:
int && ref = 2;
std::cout << ref;
This code is perfectly valid. Notice, however, that here the compiler also creates a temporary object of type int and initalises it with 2. This is this temporary that ref binds to. However, if the temporary's lifetime was limited to the instruction it is created within, and ended on the semicolon that marks the end of instruction, the next instruction would be a disaster, as cout would be using a dangling reference. Thus, references to temporaries like the one above would be rather impractical. This is what the "extension of the lifetime of a temporary" is needed for. I suspect that the compiler, upon seeing something like int && ref = 2 is allowed to transform it to something like this
int tmp = 2;
int && ref = std::move(tmp);
std::cout << ref; // equivalent to std::cout << tmp;
Without lifetime expansion, this could look rather like this:
{
int tmp = 2;
int && ref = std::move(tmp);
}
std::cout << ref; // what is ref?
Doing such a trick in a return statement would be pointless. There's no reasonable, safe way to extend the lifetime of any object local to a function.
BTW. Most modern compilers issue a warning and reduce your function
int&& func()
{
return 42;
}
to
int&& func()
{
return nullptr;
}
with an immediate segfault upon any attempt to dereference the return value.
Is the following UB (undefined-behavior) ?
Is it really one level "too much" that value is now dangling and there is no lifetime extension done by the compiler/language rules?
const int &get_value(const int &value) { return value; };
int main()
{
const auto &value = get_value(5);
printf("Value is: %d", value);
}
Yes, this is UB. When passing by 5 to get_value(), a temporary object is created and function parameter const reference value bind to it. Since, the temporary object bound to function parameter value, it will persist until the completion of the full expression containing the call. In the main(), you are dereferencing a reference which is not bound to a living object and this is undefined bahavior.