This is a very minimal example:
class Foo
{
public:
Foo(int x) {};
};
void ProcessFoo(Foo& foo)
{
}
int main()
{
ProcessFoo(Foo(42));
return 0;
}
The above compiles fine on Visual Studio, but generates an error on Linux and Mac.
Compiling the above generates this:
$ g++ -std=c++11 -c newfile.cpp
newfile.cpp: In function ‘int main()’:
newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
ProcessFoo(Foo(42));
^
newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
void ProcessFoo(Foo& foo)
I've found three workarounds:
Create a temp variable for the invocation of ProcessFoo.
Like this:
Foo foo42(42);
ProcessFoo(foo42);
ProcessFoo takes a const reference: void ProcessFoo(const Foo& foo)
ProcessFoo just lets Foo get passed by value. void ProcessFoo(Foo foo)
Why is the compiler forbidding my original code? (What is it guarding against)? What is it about each of the three workarounds above that satisfies the compiler? What would MSVC allow it, but not g++?
By design, C++ only allows a temporary to be passed to a const reference, value, or rvalue reference. The idea is that a function taking a non-const reference parameter is stating that it wants to modify the parameter and allowing it to go back to the caller. Doing so with a temporary is meaningless and most likely an error.
And I don't know what version of g++ you're running. It doesn't work here: http://coliru.stacked-crooked.com/a/43096cb398cbc973
Why is the compiler forbidding my original code?
Because it is forbidden by the Standard:
8.5.3 References 5
...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.
[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
...
'
What is it guarding against?
Inadvertently modifying an object that is going to be destructed after the function call.
What is it about each of the three workarounds above that satisfies the compiler?
1 Creates a named object and 3 a copy.
2 Works because the lifetime of the object is simply extended, and changes to it are prevented at the same time.
What would MSVC allow it, but not g++?
Because it is a language extension. Disable it by going to Property Pages->C/C++->Language->Disable Language Extensions and you'll get an error.
Why is the compiler forbidding my original code?
MSVC has an extension that allows temporaries to bind to non-const lvalue-references. Of course this isn't a standard-conforming feature so I would stay away from it to be portable. For example, it doesn't work with the latest versions of GCC and Clang as you've seen.
What is it about each of the three workarounds above that satisfies the compiler?
Back in C++03, expressions could only be lvalues or rvalues. References could only designate the "lvalueness" of an object, and so it was used with the intention of aliasing a preexisting object. By contrast, rvalues don't exist beyond the expression in which they appear. Also, the end result of references was normally to copy or modify the object, and it doesn't make much sense to the language to modify an rvalue like 55 for example.
The rules allow you to bind an rvalue to a lvalue-reference to const, in which case the temporary's lifetime is extended to the lifetime of the reference. When you take an object by value the object is copied.
With C++11 we have rvalue-references and xvalues which were made for the purpose of exchanging ownership. With this is lessens the usefulness of lvalue-references to const. Moreover, taking by-value causes a move if it is an rvalue.
Once you declared the prototype for ProcessFoo as
void ProcessFoo(Foo& foo)
You are conveying your intent as the formal parameter "foo" is subject to modification as it is not being passed by const &.
At the call-site,
ProcessFoo(Foo(42));
Foo(42) is creating a temporary stack object that is not modifiable. It is okay to pass-by-value or pass-by-ref-to-const to a method.
As you listed yourself, satisfying those constraints, makes the compiler happy.
is giving you an object that is not compiler generated and is under your control.
Informs the compiler that the method guarantees const-ness of the object.
Informs the compiler that this (temporary) object is passed by value and hence no issues.
Related
This is a very minimal example:
class Foo
{
public:
Foo(int x) {};
};
void ProcessFoo(Foo& foo)
{
}
int main()
{
ProcessFoo(Foo(42));
return 0;
}
The above compiles fine on Visual Studio, but generates an error on Linux and Mac.
Compiling the above generates this:
$ g++ -std=c++11 -c newfile.cpp
newfile.cpp: In function ‘int main()’:
newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
ProcessFoo(Foo(42));
^
newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
void ProcessFoo(Foo& foo)
I've found three workarounds:
Create a temp variable for the invocation of ProcessFoo.
Like this:
Foo foo42(42);
ProcessFoo(foo42);
ProcessFoo takes a const reference: void ProcessFoo(const Foo& foo)
ProcessFoo just lets Foo get passed by value. void ProcessFoo(Foo foo)
Why is the compiler forbidding my original code? (What is it guarding against)? What is it about each of the three workarounds above that satisfies the compiler? What would MSVC allow it, but not g++?
By design, C++ only allows a temporary to be passed to a const reference, value, or rvalue reference. The idea is that a function taking a non-const reference parameter is stating that it wants to modify the parameter and allowing it to go back to the caller. Doing so with a temporary is meaningless and most likely an error.
And I don't know what version of g++ you're running. It doesn't work here: http://coliru.stacked-crooked.com/a/43096cb398cbc973
Why is the compiler forbidding my original code?
Because it is forbidden by the Standard:
8.5.3 References 5
...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.
[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
...
'
What is it guarding against?
Inadvertently modifying an object that is going to be destructed after the function call.
What is it about each of the three workarounds above that satisfies the compiler?
1 Creates a named object and 3 a copy.
2 Works because the lifetime of the object is simply extended, and changes to it are prevented at the same time.
What would MSVC allow it, but not g++?
Because it is a language extension. Disable it by going to Property Pages->C/C++->Language->Disable Language Extensions and you'll get an error.
Why is the compiler forbidding my original code?
MSVC has an extension that allows temporaries to bind to non-const lvalue-references. Of course this isn't a standard-conforming feature so I would stay away from it to be portable. For example, it doesn't work with the latest versions of GCC and Clang as you've seen.
What is it about each of the three workarounds above that satisfies the compiler?
Back in C++03, expressions could only be lvalues or rvalues. References could only designate the "lvalueness" of an object, and so it was used with the intention of aliasing a preexisting object. By contrast, rvalues don't exist beyond the expression in which they appear. Also, the end result of references was normally to copy or modify the object, and it doesn't make much sense to the language to modify an rvalue like 55 for example.
The rules allow you to bind an rvalue to a lvalue-reference to const, in which case the temporary's lifetime is extended to the lifetime of the reference. When you take an object by value the object is copied.
With C++11 we have rvalue-references and xvalues which were made for the purpose of exchanging ownership. With this is lessens the usefulness of lvalue-references to const. Moreover, taking by-value causes a move if it is an rvalue.
Once you declared the prototype for ProcessFoo as
void ProcessFoo(Foo& foo)
You are conveying your intent as the formal parameter "foo" is subject to modification as it is not being passed by const &.
At the call-site,
ProcessFoo(Foo(42));
Foo(42) is creating a temporary stack object that is not modifiable. It is okay to pass-by-value or pass-by-ref-to-const to a method.
As you listed yourself, satisfying those constraints, makes the compiler happy.
is giving you an object that is not compiler generated and is under your control.
Informs the compiler that the method guarantees const-ness of the object.
Informs the compiler that this (temporary) object is passed by value and hence no issues.
This is a very minimal example:
class Foo
{
public:
Foo(int x) {};
};
void ProcessFoo(Foo& foo)
{
}
int main()
{
ProcessFoo(Foo(42));
return 0;
}
The above compiles fine on Visual Studio, but generates an error on Linux and Mac.
Compiling the above generates this:
$ g++ -std=c++11 -c newfile.cpp
newfile.cpp: In function ‘int main()’:
newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
ProcessFoo(Foo(42));
^
newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
void ProcessFoo(Foo& foo)
I've found three workarounds:
Create a temp variable for the invocation of ProcessFoo.
Like this:
Foo foo42(42);
ProcessFoo(foo42);
ProcessFoo takes a const reference: void ProcessFoo(const Foo& foo)
ProcessFoo just lets Foo get passed by value. void ProcessFoo(Foo foo)
Why is the compiler forbidding my original code? (What is it guarding against)? What is it about each of the three workarounds above that satisfies the compiler? What would MSVC allow it, but not g++?
By design, C++ only allows a temporary to be passed to a const reference, value, or rvalue reference. The idea is that a function taking a non-const reference parameter is stating that it wants to modify the parameter and allowing it to go back to the caller. Doing so with a temporary is meaningless and most likely an error.
And I don't know what version of g++ you're running. It doesn't work here: http://coliru.stacked-crooked.com/a/43096cb398cbc973
Why is the compiler forbidding my original code?
Because it is forbidden by the Standard:
8.5.3 References 5
...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.
[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
...
'
What is it guarding against?
Inadvertently modifying an object that is going to be destructed after the function call.
What is it about each of the three workarounds above that satisfies the compiler?
1 Creates a named object and 3 a copy.
2 Works because the lifetime of the object is simply extended, and changes to it are prevented at the same time.
What would MSVC allow it, but not g++?
Because it is a language extension. Disable it by going to Property Pages->C/C++->Language->Disable Language Extensions and you'll get an error.
Why is the compiler forbidding my original code?
MSVC has an extension that allows temporaries to bind to non-const lvalue-references. Of course this isn't a standard-conforming feature so I would stay away from it to be portable. For example, it doesn't work with the latest versions of GCC and Clang as you've seen.
What is it about each of the three workarounds above that satisfies the compiler?
Back in C++03, expressions could only be lvalues or rvalues. References could only designate the "lvalueness" of an object, and so it was used with the intention of aliasing a preexisting object. By contrast, rvalues don't exist beyond the expression in which they appear. Also, the end result of references was normally to copy or modify the object, and it doesn't make much sense to the language to modify an rvalue like 55 for example.
The rules allow you to bind an rvalue to a lvalue-reference to const, in which case the temporary's lifetime is extended to the lifetime of the reference. When you take an object by value the object is copied.
With C++11 we have rvalue-references and xvalues which were made for the purpose of exchanging ownership. With this is lessens the usefulness of lvalue-references to const. Moreover, taking by-value causes a move if it is an rvalue.
Once you declared the prototype for ProcessFoo as
void ProcessFoo(Foo& foo)
You are conveying your intent as the formal parameter "foo" is subject to modification as it is not being passed by const &.
At the call-site,
ProcessFoo(Foo(42));
Foo(42) is creating a temporary stack object that is not modifiable. It is okay to pass-by-value or pass-by-ref-to-const to a method.
As you listed yourself, satisfying those constraints, makes the compiler happy.
is giving you an object that is not compiler generated and is under your control.
Informs the compiler that the method guarantees const-ness of the object.
Informs the compiler that this (temporary) object is passed by value and hence no issues.
Consider the following:
struct my_type {};
my_type make_my_type() { return my_type{}; }
void func(my_type&& arg) {}
int main()
{
my_type&& ref = make_my_type();
func(ref);
}
Needless to say, this code doesn't compile. I realise that I need to use std::move() in the second function call, but for the purposes of understanding I want to consider the code as it is.
Attempting to compile the above, Clang 3.5 tells me:
error: no matching function for call to 'func'
note: candidate function not viable: no known conversion from 'my_type' to 'my_type &&' for 1st argument void func(my_type&&) {}
While g++ 4.9 says something almost identical:
error: cannot bind 'my_type' lvalue to 'my_type&&'
note: initializing argument 1 of 'void func(my_type&&)'
These error messages have me rather confused, because while ref is certainly an lvalue, its type is still my_type&&... isn't it?
I'm trying to understand exactly what's going on here, so I'm wondering which (if any) of the following are true:
Since only rvalues can be bound to rvalue references, and ref is an lvalue, it cannot be bound to arg. The error messages from both Clang and g++ are misleading in claiming that ref is a (non-reference) my_type that "cannot be converted".
Because it is an lvalue, ref is treated for the purposes of overload resolution as a non-reference my_type, despite its actual type being my_type&&. The error messages from Clang and g++ are misleading because they are displaying the type as used internally for function matching, not the real type of ref.
In the body of main(), the type of ref is plain my_type, despite the fact I explicitly wrote my_type&&. So the error messages from the compilers are accurate, and it is my expectation that is wrong. This doesn't seem to be the case however, since
static_assert(std::is_same<decltype(ref), my_type&&>::value, "");
passes.
There is some other magic going on that I haven't considered.
Just to repeat, I know that the solution is to use std::move() to convert the rref back into an rvalue; I'm looking for an explanation of what's going on "behind the scenes".
Consider these three assignments:
my_type x = func_returning_my_type_byvalue();
my_type & y = func_returning_my_type_byvalue();
my_type && z = func_returning_my_type_byvalue();
The first - you have a local variable x and it's being initialized to the result of a function call (rvalue) so a move constructor/assignment can be used or the construction of x could be elided entirely (skipped over and x is constructed in-place by func_returning_my_type_byvalue when it generates its result).
Note that x is an lvalue - you can take its address, so therefore it is also a type of reference itself. Technically all variables that are not references, are references to themselves. In that respect lvalues are a binding site for assignments to and reads from known-storage-duration memory.
The second will not compile - you cannot assign a reference to a result (this way), you must use reference assignment syntax to alias an existing lvalue. It is perfectly fine to do this, however:
my_type & y = func_returning_my_type_byreference();
// `y` will never use constructors or destructors
This is why the third exists, when we need a reference to something we cannot create a reference to using the conventional syntax. Within something like func in the original question, the lifetime of arg is not immediately obvious. For example we can't do this without an explicit move:
void func( my_type && arg ) {
my_type && save_arg = arg;
}
The reason this is not allowed is because arg is a reference to a value first and foremost. If the storage of arg's value (what it's referring to) were to be shorter than that of save_arg, then save_arg would call the destructor of that value - in effect capturing it. That is not the case here, save_arg will disappear first, so it makes no sense to transfer an lvalue into it that we can, after func, still refer to potentially!
Consider that even if you were to use std:move to force this to compile. The destructor will still not be called within func because you haven't created a new object, just a new reference, and then this reference is destroyed before the original object itself went out of scope.
For all intents and purposes arg behaves as if it's my_type&, as do any rvalue references. The trick is storage duration and the semantics of lifetime extension by reference passing. It's all regular references under the hood, there is no 'rvalue type'.
If it helps, recall the increment / decrement operators. There are two overloads that exist, not two operators. operator++(void) (pre) and operator++(int) (post). There is never an actual int being passed, it's just so the compiler has different signatures for different situations / contexts / agreements about value treatment. This is sort of the same deal with references.
If rvalue and lvalue references are both always referred to like an lvalue, what's the difference?
In a word: object lifetime.
An lvalue reference must always be assigned to using something with longer storage duration, something that is already constructed. That way there is no need to call constructors or destructors for the scope of the lvalue reference variable, because by definition we are given a ready object and forget about it before it's due to be destroyed.
it's also relevant that objects are implicitly destroyed in the reverse order they're defined:
int a; // created first, destroyed last
int b; // created second, destroyed 2nd-last
int & c = b; // fine, `c` goes out of scope before `b` per above
int && d = std::move(a); // fine, `a` outlives `d`, same situation as `c`
If we assigned to an rvalue reference, something that is an lvalue reference, the same rule applies - the lvalue must by definition have longer storage, so we don't need to call constructors or destructors for c or even d. You can't trick the compiler with std::move on this because it knows the scope of the object being moved - d is unambiguously shorter-duration than the reference it's being given, we're just forcing the compiler to use the rvalue type check / context and that's all we've achieved.
The difference is with non-lvalue references - things like expressions where there can be references to them but these references are definitely short-lived, perhaps shorter than the duration of a local variable. Hint Hint.
When we assign the result of a function call or an expression to an rvalue reference, we are creating a reference to a temporary object that otherwise could not be referred to. Due to this, we are in effect forcing in-place construction of a variable from the result of an expression. This is a variation on copy/move elision where the compiler has no choice but to elide the temporary to in-place construction:
int a = 2, b = 3; // lvalues
int && temp = a + b; // temp is constructed in-place using the result of operator+(int,int)
The case with func
It boils down to an lvalue assignment - references as function arguments refer to objects that may exist for longer than a function call, and as such are lvalues even when the argument type is an rvalue reference.
The two cases are:
func( std::move( variable ) ); // case 1
func( my_type() + my_type() ); // case 2
func is not allowed to guess which situation we will use it in ahead of time (sans optimizations). If we didn't allow case 1, then there would be a legitimate reason to consider an rvalue reference parameter as having less storage duration than the function call, but that would also make no sense because either the object is always cleaned up inside func or always outside of it, and having "unknown" storage duration at compile time is not satisfactory.
The compiler has no choice but to assume the worst, that case 1 might happen eventually, in which case we must make guarantees to the storage duration of arg as being longer than the call to func in the general case. As consequence of this - that arg would be considered to exist for longer than the call to func some of the time, and that func's generated code must work in both cases - arg's allowable usage and assumed storage duration meet the requirements of my_type& and not my_type&&.
Once upon a time, I assumed that code like this would fail:
const MyClass& obj = MyClass();
obj.DoSomething();
because the MyClass object would be destroyed at the end of its full-expression, leaving obj as a dangling reference. However, I learned (here) that this isn't true; the standard actually has a special provision that allows const references to keep temporaries alive until said references are destroyed themselves. But, it was emphasized, only const references have this power. Today I ran the code below in VS2012 as an experiment.
struct Foo
{
Foo() { std::cout << "ctor" << std::endl; }
~Foo() { std::cout << "dtor" << std::endl; }
};
void f()
{
Foo& f = Foo();
std::cout << "Hello world" << std::endl;
}
The output when calling f() was:
ctor
Hello world
dtor
So I had a look at the C++11 draft standard, and only found this (§ 12.2/4):
There are two contexts in which temporaries are destroyed at a
different point than the end of the full-expression. The first context [doesn't
apply]. 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 of a subobject to which the
reference is bound persists for the lifetime of the reference.
The word const is conspicuously absent from the above. So; has this behavior been changed for C++11, was I wrong about the const thing to begin with, or does VS2012 have a bug and I just haven't found the relevant part of the standard?
The behavior hasn't changed, you just need to turn your warning level up to /W4. VisualStudio implements the lifetime extension rule even for non-const lvalue references as a compiler extension. In this context, binding an rvalue to the non-const reference behaves the same as if you were binding it to a const reference.
With /W4 you'd see this:
warning C4239: nonstandard extension used : 'initializing' : conversion from 'Foo' to 'Foo &'
1> A non-const reference may only be bound to an lvalue
The text disallowing binding of an rvalue to a non-const lvalue reference can be found in §8.5.3/5
— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference.[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
int i = 2;
double& rd3 = i; // error: type mismatch and reference not const
—end example ]
The second half of the quoted statement is what allows binding of a temporary to an rvalue reference, as shown in litb's answer.
string &&s = string("hello");
This, combined with the lifetime extension rule in §12.2/5, means the lifetime of the temporary will now match the lifetime of the (rvalue) reference it is bound to.
The word const was never present in this section. The rule
has always been (from as long as I can remember) that
a temporary used to initialize a reference has its lifetime
extended to match that of the reference, regardless of the type
of the reference.
Sometime in the late 1980's (very pre-standard), C++ introduced
the rule that a temporary could not be used to initialize
a non-const reference. Initializing a non-const reference with
a temporary would still extend the lifetime (presumably), but
since you couldn't do it... Most compilers implemented
a transition period, in which such an initialization would only
emit a warning (and the lifetime was extended).
For some reason, when Microsoft finally decided to implement
C++ (some time in the early 1990's), they decided not to
implement the new rule, and allowed initialization of
a non-const reference with a temporary (without even a warning,
at a time when most other vendors were gradually turning the
warning into an error). And of course, the implemented the
usual lifetime rule.
Finally, in C++11, new types of references were introduced,
which allowed (or even required) initialization with
a temporary. The rule about the lifetime of temporaries hasn't
changed, though; a temporary which is used to initialize
a reference (regardless of the type of reference) has its
lifetime extended.
(With a few exceptions: I would not recommend using a temporary
to initialize a class member reference in an initialization
list.)
No, because rvalue references don't need to be const, so the Standard quote is correct
string &&s = string("hello");
The lifetime is still enlargened. The constraints that make the code invalid for non-const lvalue reference is at clause 8 (notice that it is not the right place to just add "const" and "rvalue reference" etc in the paragraph you quoted. You need an active rejection of such bindings, not just saying that the lifetime of such bindings are not enlarged because you would leave the binding itself still wellformed).
I'm told that, in C++03, temporaries are implicitly non-modifiable.
However, the following compiles for me on GCC 4.3.4 (in C++03 mode):
cout << static_cast<stringstream&>(stringstream() << 3).str();
How is this compiling?
(I am not talking about the rules regarding temporaries binding to references.)
I'm told that, in C++03, temporaries are implicitly non-modifiable.
That is not correct. Temporaries are created, among other circumstances, by evaluating rvalues, and there are both non-const rvalues and const rvalues. The value category of an expression and the constness of the object it denotes are mostly orthogonal 1. Observe:
std::string foo();
const std::string bar();
Given the above function declarations, the expression foo() is a non-const rvalue whose evaluation creates a non-const temporary, and bar() is a const rvalue that creates a const temporary.
Note that you can call any member function on a non-const rvalue, allowing you to modify the object:
foo().append(" was created by foo") // okay, modifying a non-const temporary
bar().append(" was created by bar") // error, modifying a const temporary
Since operator= is a member function, you can even assign to non-const rvalues:
std::string("hello") = "world";
This should be enough evidence to convince you that temporaries are not implicitly const.
1: An exception are scalar rvalues such as 42. They are always non-const.
First, there's a difference between "modifying a temporary" and "modifying an object through an rvalue". I'll consider the latter, since the former is not really useful to discuss [1].
I found the following at 3.10/10 (3.10/5 in C++11):
An lvalue for an object is necessary
in order to modify the object except
that an rvalue of class type can also
be used to modify its referent under
certain circumstances. [Example: a
member function called for an object
(9.3) can modify the object. ]
So, rvalues are not const per-se but they are non-modifiable under all but some certain circumstances.
However, that a member function call can modify an rvalue would seem to indicate to me that the vast majority of cases for modifying an object through an rvalue are satisfied.
In particular, the assertion (in the original question I linked to) that (obj1+obj2).show() is not valid for non-const show() [ugh, why?!] was false.
So, the answer is (changing the question wording slightly for the conclusion) that rvalues, as accessed through member functions, are not inherently non-modifiable.
[1] - Notably, if you can obtain an lvalue to the temporary from the original rvalue, you can do whatever you like with it:
#include <cstring>
struct standard_layout {
standard_layout();
int i;
};
standard_layout* global;
standard_layout::standard_layout()
{
global = this;
}
void modifying_an_object_through_lvalue(standard_layout&&)
{
// Modifying through an *lvalue* here!
std::memset(global, 0, sizeof(standard_layout));
}
int main()
{
// we pass a temporary, but we only modify it through
// an lvalue, which is fine
modifying_an_object_through_lvalue(standard_layout{});
}
(Thanks to Luc Danton for the code!)