I know this is valid in C++, because temporary from std::string("xxx") lives to the end of the full expression it appears in.
printf("%s\n", std::string("xxx").c_str());
And this is valid too:
std::string f1() {
return std::string("xxx");
}
void g1() {
printf("%s\n", f1().c_str());
}
But is this valid? if not, why?
const char* f2() {
return std::string("xxx").c_str();
}
void g2() {
printf("%s\n", f2());
// or: std::string x = f2(); // is this valid?
}
The temporary std::string inside of f2 is destroyed when the function returns. The char* will be dangling.
EDIT: In response to the comment, std::string x = f2() is also not valid. It's still bad to initialize a std::string from a dangling pointer.
The exact same rule applies. The full-expression in which the std::string was created in f2 is
std::string("xxx").c_str()
together with the construction of the return value from it.
After this expression executed and the construction of the return value executed, the temporary std::string("xxx") will be destroyed. Since destruction of the temporary std::string makes the const char* obtained from .c_str() and copied into the return value invalid, the return value of the function cannot be used in any way.
For f1 the same applies (details relating to temporary materialization and copy elision, especially in C++17, aside). The temporary std::string is destroyed after construction of the return value. But in this case this is not a problem, because the return value is not a pointer to something that was destroyed, but an independent std::string object itself. This std::string object will live until the end of the full-expression in which the call to f1 appears, i.e. until the end of
printf("%s\n", f1().c_str())
The following would be fine as well:
std::string f3() {
return std::string("xxx").c_str();
}
Here again, a new std::string is created in the return value, which will live until the end of the full-expression in which f3 is called.
In practice, for f1, since std::string("xxx"); is already a prvalue of type std::string, copy elision will be applied, so that there is really only one temporary object, the return value of f1. The intermediate temporary will be eliminated and the return value directly constructed from "xxx". This is mandatory since C++17 and was allowed and common before C++17.
Related
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;).
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.
Consider the following c++ programs:
string construct(string&& s) {
// Passing a r-value reference as an argument to the assignment operator
string constructed = s;
return constructed;
}
int main() {
string original = "Hello";
string temp1 = construct(std::move(original));
printf("%s\n", original.c_str()); // Prints "Hello", so original has not changed
return 0;
}
Now a small change that I perform is calling std::move on an r-value reference argument:
string constructWithMove(string&& s) {
// Passing a cast to an r-value reference using r-value reference as an argument.
string constructed = std::move(s);
return constructed;
}
int main() {
string original = "Hello";
string temp = constructWithMove(std::move(original));
printf("%s\n", original.c_str()); // Prints "", original is set to the empty string, WHY???
return 0;
}
So it looks like casting an r-value reference to an r-value reference induces something peculiar. Why in the first case the original string retains its value but not in the second?
What happens when std::move is called on a rvalue reference?
std::move casts the argument into an rvalue reference, which it returns.
Is std::move(r_value_reference_argument) undefined
No.
// Prints "", original is set to the empty string, WHY???
What's happening here?
Because the result from std::move is an r-value (x-value to be more specific). And being passed to a constructor of std::string, it invokes the move constructor. You are observing a string that was moved from.
Note that this moving leaves the original string in an unspecified state. It is not guaranteed to be empty, nor even different from what it used to contain. Neither is it guaranteed to not be empty.
OK, but why in the first case the original string is not empty?
Because s is an lvalue1, and therefore the copy constructor was used. The copy constructor leaves the original string unmodified.
1 Easy rule of thumb: If it is a name, then it is an l-value.
string constructed = s;
This does not cause a move because s is not an rvalue. It is an rvalue reference, but not an rvalue. If it has a name, it is not an rvalue. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.
string constructed = std::move(s);
This causes a move because std::move(s) is an rvalue: it's a temporary and its type is not lvalue reference.
There are no other moves in the program (std::move is not a move, it's a cast).
// Prints "", original is set to the empty string, WHY???
Because you moved it.
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
From this thread it is clear that a string literal cannot be used for a function that returns const string& since there is an implicit conversion from const char* to std::string which creates a temporary.
But then why do I get a warning "warning: returning reference to a temporary" if my return type match exactly and there is no need for conversion, e.g.:
include <iostream>
const int& test(){
return 2;
}
int main(){
std::cout << test();
}
No implicit conversion needed to happen on the return value of 2, so why is there a warning? I thought using test() would be pretty much the same as doing
const int& example = 2;
which is perfectly valid. Additionally if I change 2 to 2.2 (so it is a double) the program still runs (with the same warning) despite the fact there IS a conversion from double to int? Shouldn't I be running in to an issue similar to how const char* was returned to the string reference, if there is a conversion from double to int?
A temporary is still created. §8.5.3/(5.2.2.2) applies1:
Otherwise, a temporary of type “ cv1 T1” is created and
copy-initialized (8.5) from the initializer expression. The reference
is then bound to the temporary.
This also applies in your second example. It does not apply for prvalues of class type, or scalar xvalues: Both
const A& a = A();
// and
const int& i = std::move(myint);
do not introduce a temporary. However, that isn't changing the final result : In any case, the temporary that is bound to the reference will be destroyed at the end of the return statement - §12.2/(5.2):
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.
That is, the temporary is destroyed before the function even exits, and thus your program induces undefined behavior.
1 I could go on and quote the entire list to show why it does, but that would presumably be a waste of answer space.
an implicit conversion from const char* to std::string which creates a temporary
You are conflating the two topics. Implicit conversions are not the only thing that can create temporaries:
const std::string& test() {
return std::string("no implicit conversions here.");
}
There is no implicit conversion to create the string, but the created string is still temporary, and so you're still returning a reference to a temporary object.
In your example, 2 is still a temporary value, temporarily stored somewhere on the stack, and whose location/address is returned so callers can get at the value, but they are not supposed to.