Is the lifetime of a reference extended? - c++

I'd like to pass a reference into a function. This code does not work, as I'd expect:
struct A {
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A());
}
I get the compile error
invalid initialization of non-const reference of type A& from an rvalue of type A
But when I just add the method A& ref() to A like below and call it before passing it along, it seems I can use a. When debugging, the A object is destroyed after foo() is called:
struct A {
A& ref() {
return *this;
}
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A().ref());
}
Is this valid code according to the standard? Does calling ref() magically extend the lifetime of the object until foo() returns?

Your code is perfectly valid.
In this line
foo(A().ref());
The instance of a temporary A lives until the end of the statement (;).
That's why it's safe to pass A& returned from ref() to foo (as long as foo doesn't store it).
ref() by itself does not extend any lifetime, but it helps by returning an lvalue reference.
What happens in the case of foo(A()); ? Here the temporary is passed as an rvalue. And in C++ an rvalue does not bind to non-const lvalue references (even in C++11, an rvalue reference does not bind to non-const lvalue references).
From this Visual C++ blog article about rvalue references:
... C++ doesn't want you to accidentally modify temporaries, but directly
calling a non-const member function on a modifiable rvalue is explicit, so
it's allowed ...

A() creates a temporary object of type A. The object exists until the end of the full expression in which it is created. The problem in your original code is not the lifetime of this temporary; it's that the function takes its argument as a non-const reference, and you're not allowed to pass a temporary object as a non-const reference. The simplest change is for foo to take it's argument by const reference, if that's appropriate to what the function does:
void foo(const A&);
int main() {
foo(A());
}

There are several questions in this question. I'll attempt to address all of them:
First, you cannot pass a temporary (prvalue) of type A to a function taking A& because non-const lvalue references cannot bind to rvalues. That's a language restriction. If you want to be able to pass a temporary, you either need to take a parameter of type A&& or of type A const& - the latter since temporaries can bind to const lvalue references.
Is this valid code according to the standard? Does calling ref() magically extend the lifetime of the object until foo() returns?
There is no lifetime extension going on in your program at all. From [class.temp]:
There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array with
no corresponding initializer (8.6). The second context is when a copy constructor is called to copy an element
of an array while the entire array is copied (5.1.5, 12.8). [...] The third context is when a reference is bound to a temporary.
None of those contexts apply. We are never binding a reference to a temporary in this code. ref() binds *this to an A&, but *this is not a temporary, and then that resulting reference is simply passed into foo().
Consider this variant of your program:
#include <iostream>
struct A {
A& ref() { return *this; }
~A() { std::cout << "~A()\n"; }
};
int main() {
auto& foo = A().ref();
std::cout << "----\n";
}
which prints
~A()
----
illustrating that there is no lifetime extension.
If instead of binding the result of ref() to a reference we instead bound a member:
#include <iostream>
struct A {
A& ref() { return *this; }
int x;
~A() { std::cout << "~A()\n"; }
};
int main() {
auto&& foo = A().x;
std::cout << "----\n";
}
then we actually are binding a temporary to a reference and that third context applies - the lifetime of the complete object of the subobject to which the reference is bound is persisted for the lifetime of the reference. So this code prints:
----
~A()

Related

Mixing Rvalue and LValue reference

I'm trying to better understand LValue, RValue, and how std::move works.
I have the following code
#include <string>
class A
{
public:
A() = default;
A(std::string&& aString): myString(std::move(aString)) {}
std::string myString;
};
class B
{
public:
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
private:
A myA;
};
int main()
{
B b;
std::string hello;
b.InitMembers(hello);
}
my questions are:
In void InitMembers(string& aString) { myA = A(std::move(aString));} I understand that I have to use std::move to aString in order to cast aString from an LValue reference to a RValue reference. But I have some doubts regarding the meaning of aString in the InitMember scope. aString is provided as an LValue reference, but in the method scope it's considered as an LValue and that's why I have to use the std::move? Std::move should rely on reference deduction (right?), how does it deduce the type in this case? It will deduce a type "string" o "string&" since aString is provided as a LValue Reference in the method's arguments?
Why do I have to use std::move also in the constructor initializer of A? Shouldn't be enough the fact that aString is an RValue reference, which triggers the move constructor?
Isn't the following implementation good as the one above?
#include <string>
class A
{
public:
A() = default;
A(std::string& aString): myString(std::move(aString)) {}
std::string myString;
};
class B
{
public:
void InitMembers(std::string& aString) { myA = A(aString);}
private:
A myA;
};
int main()
{
B b;
std::string hello;
b.InitMembers(hello);
}
Thanks :)
Concerning
A(std::string&& aString): myString(std::move(aString)) {}
std::string&& denotes an rvalue reference to a std::string. Rvalue references only bind to rvalues (both prvalues and xvalues), so the two possible call sites will be like this:
// assuming this is defined somewhere
std::string f(void); // returns by value, i.e. `f()` is an rvalue
// call site 1
A a1{f()}; // `f()` is an rvalue (precisely a prvalue)
// assuming this
std::string s{"ciao"}; // s is an lvalue
// call site 2
A a2{std::move(s)}; // `std::move(s)` is an rvalue (precisely an xvalue)
// i.e. with `std::move` we turn s into an rvalue argument,
// so it can bind to the rvalue reference parameter
// don't expect s to be the one it was before constructing a2
In either case, what does the constructor do with aString?
Well, aString is an lvalue, because "it has a name" (the actual definition is a bit more complicated, but this is the easiest one to get started with, and it isn't all that wrong after all), so if you use it verbatim, the compiler won't assume it is bound to a temporary and it won't let myString steal it resources.
But you know that aString is bound to a temporary, because you've declared it as std::string&&, so you pass it as std::move(aString) to tell the compiler "treat this is a temporary".
Yes, technically also the compiler knows that aString is bound to a temporary, but it can't std::move it automatically. Why? Because you might want to use it more than once:
A(std::string&& aString) : myString(aString/* can't move this */) {
std::cout << aString << std::endl; // if I want to use it here
}
// yes, this is a very silly example, sorry
As regards
void InitMembers(std::string& aString) { myA = A(std::move(aString));}
aString denotes an lvalue reference to non-const std::string, so you can pass to .InitMembers only non-const lvalues.
Then inside the function you're std::moveing it to tell A's constructor "look, this is a temporary". But that also means that at the call site (b.InitMembers(hello);) you're leaving the input (hello) in a moved-from state, just like the s in the first example above. That's ok, because the caller knows that InitMembers takes its parameter by non-const lvalue reference, so it is aware that the argument they pass can be changed by the call. Just like in the previous example it's the user who's writing std::move around s, so they're supposed to know what they do.
For more details about how std::move works (and std::forward as well), I want to point you to this answer of mine.

Why is Copy constructor called with rvalue

In the below code, even though Account(20, "Dave") at Line2 is rvalue, why is the copy constructor called (Line1), instead of compiler throwing error? In case of normal functions receiving rvalue, if we use lvalue reference as input parameter, compiler throws error.
#include <iostream>
#include <string>
#include <vector>
class Account
{
private:
int num;
std::string name;
public:
Account(int lnum, std::string lname) : num {lnum}, name {lname}
{
std::cout << "\n3arg constr";
}
Account(const Account &a) : Account{a.num, a.name} //Line1
{
std::cout << "\nCopy Constr";
}
};
int main()
{
std::vector<Account> myVec {};
myVec.push_back(Account(20, "Dave")); //Line2
std::cout << std::endl;
}
Yes, rvalues are moved, lvalues are copied. But when there's no according move operation, rvalues are copied as well.
The compiler will synthesize a move constructor only for such class that doesn't define any of its own copy-control members (copy-constructor, copy-assignment, or destructor), and if all the non-static members can be moved.
If a class doesn't have a move operation, the corresponding copy operation will be used, through normal function matching (T&& can convert to const T&).
Unlike copy operations, a move operation won't be implicitly defined as deleted function. A copy is simply used instead.
As for the push_back(). Vector provides guarantees that is something goes wrong in process of the push_back(), the vector, to which we push, will be left unchanged, in the absence of noexcept move constructor for a class, a copy is used instead,
What you have in Account(const Account &a) is an lvalue reference to const. Const lvalue references can bind to rvalues. Quoting the reference (emphasis mine)
Rvalue references can be used to extend the lifetimes of temporary
objects (note, lvalue references to const can extend the lifetimes of
temporary objects too, but they are not modifiable through them):
struct Foo{};
void bar(Foo && f) {}
void baz(Foo const&) {}
void qux(Foo&) {}
int main() {
bar(Foo{}); //rvalue reference binds to rvalue
baz(Foo{}); //const rvalue reference binds to rvalue
//Error: cannot bind non-const lvalue reference of type 'Foo&' to an rvalue of type 'Foo'
// qux(Foo{});
}

Is returning a reference to a local object undefined behavior in copy initialization?

Consider the following code:
struct foo
{
foo(foo const&) = default; // To make sure it exists
};
foo& get_local_foo_reference()
{
foo my_local_foo;
return my_local_foo; // Return a reference to a local variable
}
int main()
{
foo my_foo = get_local_foo_reference();
}
Now everybody would agree that returning a reference to a local variable is bad and lead to undefined behavior.
But in the case of copy initialization (as shown in the code above) the argument is a constant lvalue reference, so it should be a reference initialization of the argument, which extends the life-time of the reference.
Is this valid, or is it still undefined behavior?
Lifetime extension only applies to temporaries when bounds to const reference or r-value reference. (temporary cannot be bound to non const l-value reference)
Even if you return temporary, it would be UB:
const foo& create_foo() { return foo{}; } // Also UB
From http://eel.is/c++draft/class.temporary#6.10:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
Lifetime extension only applies to temporaries (and subobjects thereof). my_local_foo is not a temporary.

Passing an integer to a function asking for reference

Why is this code well-formed? I'm not passing a reference to the function:
void function(const int& ref) {
}
int main()
{
function(1);
}
Constant lvalue references can bind to rvalues. Rvalues, like your literal 1, don't have a persistent alias, so if you were to modifying it, you wouldn't be able to observe the effect, but if you promise not to modify it (namely by accessing it through a constant reference), you can still have perfectly sensible code, and that's why this binding is allowed.
(You can also bind rvalues to (mutable) rvalue references: void function(int &&) In that case, the rvalue reference becomes the (unique) alias of the value.)
Note also that without this rule it would be impossible to initialize variables from functions that return prvalues, or use copy-initialization at all:
struct T { T(int); };
T f();
T x = 1; // === "T x = T(1);", copy constructor wants to bind to prvalue
T x = f(); // ditto
T x((f())); // ditto
The compiler can create a temporary from the constant and temporaries are allowed to bind to const references. If the reference wasn't const, this wouldn't be allowed.

Warning C4172: Returning a reference to const std::string bound to a local variable. How safe is it?

I was just building one of our projects at work and I see a new function was added:
const std::string& ClassName::MethodName() const
{
return "";
}
The compiler gives a warning:
Warning C4172: returning address of local variable or temporary
I think the compiler is right. How safe is this function?
Note that the function doesn't return const char* which would be OK inasmuch as string literals have static storage duration. It returns a reference to const std::string
Yes it is not safe.
Returning address of a local variable or temporary and dereferencing it results in Undefined Behavior.
As you commented:
Yes, the lifetime of the temporary bound to a constant reference increases till the lifetime of constant. But that needs the caller to accept the return value in a const reference, So by itself the function won't be safe.
From the C++ Standard:
C++03 12.2 Temporary objects:
The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below...
A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits. A temporary bound to a reference
parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.A temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits
This is an example that made things clear for me:
#include <iostream>
using std::cout;
struct A{
A() {
cout << "Ctor\n";
}
~A() {
cout << "Dtor\n";
}
};
const A& f(){
return A();
}
int main(){
const A& ref = f();
cout << "1\n";
{
const A& ref1 = A();
cout << "2\n";
}
cout << "3\n";
}
Outputs
Ctor
Dtor
1
Ctor
2
Dtor
3
Doing what you did actually does this internally in the compiler:
const std::string* ClassName::MethodName() const
{
std::string temp = "";
return &temp;
}
And returning references or pointers to local variables is bad.
There are circumstances when this code is safe. See GotW #88: A Candidate For the “Most Important const”.