How come a non-const reference cannot bind to a temporary object?
Asked Answered
P

11

257

Why is it not allowed to get non-const reference to a temporary object, which function getx() returns? Clearly, this is prohibited by C++ Standard but I am interested in the purpose of such restriction, not a reference to the standard.

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. It is clear that the lifetime of the object cannot be the cause, because constant reference to an object is not prohibited by C++ Standard.
  2. It is clear that the temporary object is not constant in the sample above, because calls to non-constant functions are permitted. For instance, ref() could modify the temporary object.
  3. In addition, ref() allows you to fool the compiler and get a link to this temporary object and that solves our problem.

In addition:

They say "assigning a temporary object to the const reference extends the lifetime of this object" and " Nothing is said about non-const references though". My additional question. Does following assignment extend the lifetime of temporary object?

X& x = getx().ref(); // OK
Publicness answered 14/10, 2009 at 11:1 Comment(11)
I disagree with "the lifetime of the object cannot be the cause" part, just because it is stated in the standard, that assigning a temporary object to the const reference extends the lifetime of this object to the lifetime of the const reference. Nothing is said about non-const references though...Stockist
Well, what the cause of that "Nothing is said about non-const references though...". It is a part of my question. Is there any sence in this? May be authors of Standard just forgot about non-const references and soon we'll see next Core Issue?Publicness
Temporaries will bind to rvalue references introduced with C++0x. I don't think this was an oversight in the standard, since rvalue references will allow completely new functionality, which would have been impossible if lvalue references bound to non-temporaries and temporaries alike (in C++0x you will have means to tell them apart, and hence use move semantics, rather than copy semantics safely).Djerba
Somebody discuss about this question here: groups.google.com/group/comp.lang.c++/browse_thread/thread/…Pender
GotW #88: A Candidate For the "Most Important const". herbsutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entryPender
Very good question - one interesting thing I've found is that MSVC (even up to VS2008) seems to accept the lines that other compilers diagnose as errors. How can that be?Marlee
@Michael: VC binds rvalues to non-const references. They call this a feature, but really it's a bug. (Note that it's not a bug because it's inherently illogical, but because it was ruled out explicitly to prevent silly errors.)Sacristy
@Sacristy - interesting... do have a pointer to some docs on that? I'd like to read up what MS might have to say about it.Marlee
@Michael: It's somewhere in MSDN. I'd have to search for it myself. (It might help to search for questions regarding this, as over the years I have seen this asked again and again. I'm sure someone somewhere has already pointed at the docs.)Sacristy
Herb Sutter's GotW #88 : A Candidate For the "Most Important const" has moved.Petrozavodsk
As of Visual Studio 2015RC it still compiles by default. With /W4 or /Wall flags, compiler gives a warning: C4239: nonstandard extension used: 'argument': conversion from 'type' to 'type&'. With "Disable language extensions" /Za it fails with error C2664: cannot convert argument 1 from 'type' to 'type&'Herein
S
113

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 ...

Basically, you shouldn't try to modify temporaries for the very reason that they are temporary objects and will die any moment now. The reason you are allowed to call non-const methods is that, well, you are welcome to do some "stupid" things as long as you know what you are doing and you are explicit about it (like, using reinterpret_cast). But if you bind a temporary to a non-const reference, you can keep passing it around "forever" just to have your manipulation of the object disappear, because somewhere along the way you completely forgot this was a temporary.

If I were you, I would rethink the design of my functions. Why is g() accepting reference, does it modify the parameter? If no, make it const reference, if yes, why do you try to pass temporary to it, don't you care it's a temporary you are modifying? Why is getx() returning temporary anyway? If you share with us your real scenario and what you are trying to accomplish, you may get some good suggestions on how to do it.

Going against the language and fooling the compiler rarely solves problems - usually it creates problems.


Edit: Addressing questions in comment:
  1. X& x = getx().ref(); // OK when will x die? - I don't know and I don't care, because this is exactly what I mean by "going against the language". The language says "temporaries die at the end of the statement, unless they are bound to const reference, in which case they die when the reference goes out of scope". Applying that rule, it seems x is already dead at the beginning of the next statement, since it's not bound to const reference (the compiler doesn't know what ref() returns). This is just a guess however.

  2. I stated the purpose clearly: you are not allowed to modify temporaries, because it just does not make sense (ignoring C++0x rvalue references). The question "then why am I allowed to call non-const members?" is a good one, but I don't have better answer than the one I already stated above.

  3. Well, if I'm right about x in X& x = getx().ref(); dying at the end of the statement, the problems are obvious.

Anyway, based on your question and comments I don't think even these extra answers will satisfy you. Here is a final attempt/summary: The C++ committee decided it doesn't make sense to modify temporaries, therefore, they disallowed binding to non-const references. May be some compiler implementation or historic issues were also involved, I don't know. Then, some specific case emerged, and it was decided that against all odds, they will still allow direct modification through calling non-const method. But that's an exception - you are generally not allowed to modify temporaries. Yes, C++ is often that weird.

Soutache answered 14/10, 2009 at 11:57 Comment(20)
1) "are temporary objects and will die any moment now". X& x = getx().ref(); // OK when will x die? 2) "I would rethink the design of my functions" My question is not about design but about a purpose of such restriction. 3) "usually it creates problems" What problems does it create in this case with non-const reference to temporary object?Publicness
@sbk: 1) Actually, the proper phrase is: "...at the end of the full expression...". A "full expression", I believe, is defined as one that's not a sub-expression of some other expression. Whether this always is the same as "the end of the statement", I'm not sure.Sacristy
@sbk: 2) Actually, you are allowed to modify rvalues (temporaries). It's forbidden for built-in types (int etc.), but it is allowed for user-defined types: (std::string("A")+"B").append("C").Sacristy
@sbk: 3) The reason Stroustrup gives (in D&E) for disallowing the binding of rvalues to non-const references is that, if Alexey's g() would modify the object (which you'd expect from a function taking a non-const reference), it would modify an object that's going to die, so nobody could get at the modified value anyway. He says that this, most likely, is an error.Sacristy
@sbi: 1) fair point, I didn't use the proper phrase. 2) I know, I know, you are nitpicking now. 3) not citing Stroustrop, but I'm trying to convey the same meaning. Thanks for the -1 anywaySoutache
To nitpick too: It shall be noted that "temporary" refers to more things. You can catch temporaries (exception objects are temporaries) by non-const reference just fine for example. So only some temporaries - those that are rvalues - cannot be bound by non-const references.Rhett
@sbk: I'm sorry if I have offended you, but I don't think 2) is nitpicking. Rvalues simply are not const unless you make them so and you can change them, unless they are built-ins. It took me a while to understand whether, with, e.g., the string example, I do something wrong (JFTR: I don't), so I tend to take this distinction serious.Sacristy
I agree with sbi - this matter is not at all nitpicking. It's all the basis of move semantics that class type rvalues are best kept non-const.Rhett
@Sacristy and litb: it's nitpicking because I have addressed your points. It's right there, saying "ignoring C++0x rvalue references" , because they are not related to the question. And second, and I'm just repeating myself here, the original idea is that it doesn't make sense to modify temporary values. Why then it is allowed to call a non-const member is another question, but I believe this is the logical order of things. I'd be happy if someone can point me to a historical document that proves me wrong. (No offense taken)Soutache
I think auto_ptr wouldn't work if we couldn't call non-const functions on class type rvalues. auto_ptr is based on operator auto_ptr_ref() being called on non-const rvalues.Rhett
@sbk: But it does make sense to be able to modify non-const temporaries. Code like f(g()+=h()) isn't that uncommon and unthinkable - and often such code relies on the ability to call non-const member functions. I'm sorry, but I still think you're wrong on that. Non-POD rvalues can be modified, and this is an important feature that most programmers take for granted without thinking - and not only in C++1x.Sacristy
@sbi: OK then, why it's not allowed to bind non-const temporaries to non-const references indeed?Soutache
@sbk: I already wrote what I remember to be the reason: "The reason Stroustrup gives (in D&E) for disallowing the binding of rvalues to non-const references is that, if Alexey's g() would modify the object (which you'd expect from a function taking a non-const reference), it would modify an object that's going to die, so nobody could get at the modified value anyway. He says that this, most likely, is an error." IOW, the reason is similar to the reason const was introduced: To prevent silly mistakes.Sacristy
@sbk, Your statement that temporaries live until the end of the statement they are contained in would mean that the following for-loop is correct: for(char const *s = string().c_str();*s;s++) ;. But it's wrong, because "string()" is destroyed not at the end of the statement, but at the end of initializing s, thus making the loop dereference not owned memory.Rhett
wrt X& x = getx().ref();, i believe that's currently a defect in the Standard: it does not appear to say that only temporaries not bound by references yet are lifetime-extended. In fact, reading the Standard literally, i find that it says the following code is not valid if temporaries bound by references are affected by those rules: struct A { A &a() { return *this; } int m; }; ... A().a().m because it says that a temporary bound to the return value of a function is destroyed when the function returns. *this refers to a temporary, and it's bound to the return value.Rhett
"The C++ committee decided it doesn't make sense to modify temporaries" Wrong.Zimmer
"You can keep passing it around "forever" just to have your manipulation of the object disappear" - here's a counterexample: passing a temporary std::ofstream to a writeto function.Periapt
@Sacristy your argument for not allowing to pass temporary objects to functions with non-const lvalue reference parameters is convincing: because we can't get the modified object, and that object is used for "output". However, can you explain why binding temporary to local non-const lvalue reference is not allowed and does not extend the lifetime of temporary object as const reference does? why myclass& ref = myclass{} is not allowed in philosophy? We can identify that temporary through the ref. Really appreciate it if you can give me some kindly guidance and help! Sincerely.Listlessness
@XIAO: I dunno. IIRC, const references extending the lifetime of temporaries is a later addition. Why they didn't allow non-const references as well I wouldn't know.Sacristy
This should be warning and not error(at max!), because passing parameter by ref does not mean, that I will modify it, may be I would like to call its non-const method that modifies other object that it holds, which is shared. This is common for scope helpers. For me it's useless limitation.Chapland
S
48

In your code getx() returns a temporary object, a so-called "rvalue". You can copy rvalues into objects (aka. variables) or bind them to to const references (which will extend their life-time until the end of the reference's life). You cannot bind rvalues to non-const references.

This was a deliberate design decision in order to prevent users from accidentally modifying an object that is going to die at the end of the expression:

g(getx()); // g() would modify an object without anyone being able to observe

If you want to do this, you will have to either make a local copy or of the object first or bind it to a const reference:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

Note that the next C++ standard will include rvalue references. What you know as references is therefore becoming to be called "lvalue references". You will be allowed to bind rvalues to rvalue references and you can overload functions on "rvalue-ness":

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

The idea behind rvalue references is that, since these objects are going to die anyway, you can take advantage of that knowledge and implement what's called "move semantics", a certain kind of optimization:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
Sacristy answered 14/10, 2009 at 13:19 Comment(5)
Hi, this is an awesome answer. Need to know one thing, the g(getx()) doesn't work because its signature is g(X& x) and get(x) returns a temporary object, so we cannot bind a temporary object (rvalue) to a non-constant reference, correct? And in your first code piece, I think it will be const X& x2 = getx(); instead of const X& x1 = getx();..Arbutus
Thank you for pointing out this bug in my answer 5 years after I wrote it! :-/ Yes, your reasoning is correct, albeit a bit backwards: We cannot bind temporaries to non-const (lvalue) references, and therefore the temporary returned by getx() (and not get(x)) cannot be bound to the lvalue reference that's the argument to g().Sacristy
Umm, what did you mean by getx() (and not get(x))?Arbutus
When I write "...getx() (and not get(x))...", I mean that the function's name is getx(), and not get(x) (as you wrote).Sacristy
This answer mixes up terminology. An rvalue is an expression category. A temporary object is an object. An rvalue may or may not denote a temporary object; and a temporary object may or may not be denoted by an rvalue.Fascicle
J
19

What you are showing is that operator chaining is allowed.

 X& x = getx().ref(); // OK

The expression is 'getx().ref();' and this is executed to completion before assignment to 'x'.

Note that getx() does not return a reference but a fully formed object into the local context. The object is temporary but it is not const, thus allowing you to call other methods to compute a value or have other side effects happen.

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);

// or more commonly
std::cout << getManiplator() << 5;

Look at the end of this answer for a better example of this

You can not bind a temporary to a reference because doing so will generate a reference to an object that will be destroyed at the end of the expression thus leaving you with a dangling reference (which is untidy and the standard does not like untidy).

The value returned by ref() is a valid reference but the method does not pay any attention to the lifespan of the object it is returning (because it can not have that information within its context). You have basically just done the equivalent of:

x& = const_cast<x&>(getX());

The reason it is OK to do this with a const reference to a temporary object is that the standard extends the lifespan of the temporary to the lifespan of the reference so the temporary objects lifespan is extended beyond the end of the statement.

So the only remaining question is why does the standard not want to allow reference to temporaries to extend the life of the object beyond the end of the statement?

I believe it is because doing so would make the compiler very hard to get correct for temporary objects. It was done for const references to temporaries as this has limited usage and thus forced you to make a copy of the object to do anything useful but does provide some limited functionality.

Think of this situation:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.

Extending the lifespan of this temporary object is going to be very confusing.
While the following:

int const& y = getI();

Will give you code that it is intuitive to use and understand.

If you want to modify the value you should be returning the value to a variable. If you are trying to avoid the cost of copying the obejct back from the function (as it seems that the object is copy constructed back (technically it is)). Then don't bother the compiler is very good at 'Return Value Optimization'

Johnathon answered 14/10, 2009 at 13:45 Comment(7)
"So the only remaining question is why does the standard not want to allow reference to temporaries to extend the life of the object beyond the end of the statement?" That is it! You understand my question. But I disagree with your opinion. You say "make the compiler very hard" but it was done for const reference. You say in your sample "Note x is an alias to a variable. What variable are you updating." No problem. There is unique variable (temporary). Some temporary object (equals to 5) must be changed.Publicness
@Martin: Dangling references aren't only untidy. They could lead to serious bugs when accessed later in the method!Gassing
@Alexey: Note that the fact that binding it to a const reference enhances a temporary's lifetimes is an exception that's been added deliberately (TTBOMK in order to allow manual optimizations). There wasn't an exception added for non-const references, because binding a temporary to a non-const reference was seen to most likely be a programmer error.Sacristy
@alexy: A reference to an invisable variable! Not that intuative.Johnathon
const_cast<x&>(getX()); doesn't make senseZimmer
@Loki: "storing the reference to temporary objects inside their objects" doesn't extend lifetime, even with const references. So that can't be the reason.Toddle
I think Tony Delroy's answer is more correct than this one. It's not that any of this is hard for the compiler, but rather than binding temporaries to non-const references creates confusing situations where functions that should have side effects appear to do nothing. An authoritative source for this is: open-std.org/jtc1/sc22/wg21/docs/papers/2002/… To answer @Bin's question, the compiler makes space on the stack for temporaries, as it does for local vairables. Note that it needs to do this whether or not they're bound.Luckett
S
16

Why is discussed in the C++ FAQ (boldfacing mine):

In C++, non-const references can bind to lvalues and const references can bind to lvalues or rvalues, but there is nothing that can bind to a non-const rvalue. That's to protect people from changing the values of temporaries that are destroyed before their new value can be used. For example:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

If that incr(0) were allowed either some temporary that nobody ever saw would be incremented or - far worse - the value of 0 would become 1. The latter sounds silly, but there was actually a bug like that in early Fortran compilers that set aside a memory location to hold the value 0.

Skysweeper answered 30/12, 2015 at 10:5 Comment(4)
Would have been funny to see the face of the programmer that was bitten by that Fortran "zero bug" ! x * 0 gives x? What? What??Carltoncarly
That last argument is particularly weak. No compiler worth mentioning would ever actually change the value of 0 to 1, or even interpret incr(0); in such a way. Obviously, if this were allowed, it would be interpreted as creating a temporary integer and passing it to incr()Estrada
This is the correct answer. This issue of dropped side effects gets worse when implicit conversions are involved. For example, suppose you changed incr(int& a) to incr(long& a). Now the expression incr(i) is converting i to a temporary long and passing it by reference. The modification inside of incr now has no effect on the caller. This would be extremely confusing. This issue was discussed in Howard Hinnant's original move semantics proposal: open-std.org/jtc1/sc22/wg21/docs/papers/2002/…Luckett
Const references avoid this particular issue, because you can't write through a const reference without shenanigans. But they can still cause other related issues. For example, if you return the const reference you were given, then the valid lifetime of the returned reference depends on whether the argument was an lvalue (in which case it's valid for some scope) or a temporary rvalue (in which case it's dead at the end of the statement). This can change silently just like the case above.Luckett
R
6

The main issue is that

g(getx()); //error

is a logical error: g is modifying the result of getx() but you don't have any chance to examine the modified object. If g didn't need to modify its parameter then it wouldn't have required an lvalue reference, it could have taken the parameter by value or by const reference.

const X& x = getx(); // OK

is valid because you sometimes need to reuse the result of an expression, and it's pretty clear that you're dealing with a temporary object.

However it is not possible to make

X& x = getx(); // error

valid without making g(getx()) valid, which is what the language designers were trying to avoid in the first place.

g(getx().ref()); //OK

is valid because methods only know about the const-ness of the this, they don't know if they are called on an lvalue or on an rvalue.

As always in C++, you have a workaround for this rule but you have to signal the compiler that you know what you're doing by being explicit:

g(const_cast<x&>(getX()));
Roseola answered 13/11, 2009 at 14:39 Comment(0)
I
6

Seems like the original question as to why this is not allowed has been answered clearly: "because it is most likely an error".

FWIW, I thought I'd show how to it could be done, even though I don't think it's a good technique.

The reason I sometimes want to pass a temporary to a method taking a non-const reference is to intentionally throw away a value returned by-reference that the calling method doesn't care about. Something like this:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

As explained in previous answers, that doesn't compile. But this compiles and works correctly (with my compiler):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

This just shows that you can use casting to lie to the compiler. Obviously, it would be much cleaner to declare and pass an unused automatic variable:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

This technique does introduce an unneeded local variable into the method's scope. If for some reason you want to prevent it from being used later in the method, e.g., to avoid confusion or error, you can hide it in a local block:

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

-- Chris

Inexact answered 22/2, 2013 at 0:54 Comment(1)
Nice example. The fact that this works suggests that the error doesn't arise as a result of a logical limitation in C++ but rather a design decision.Benedictus
B
4

Excellent question, and here's my attempt at a more concise answer (since a lot of useful info is in comments and hard to dig out in the noise.)

Any reference bound directly to a temporary will extend its life [12.2.5]. On the other hand, a reference initialized with another reference will not (even if it's ultimately the same temporary). That makes sense (the compiler doesn't know what that reference ultimately refers to).

But this whole idea is extremely confusing. E.g. const X &x = X(); will make the temporary last as long as the x reference, but const X &x = X().ref(); will NOT (who knows what ref() actually returned). In the latter case, the destructor for X gets called at the end of this line. (This is observable with a non-trivial destructor.)

So it seems generally confusing and dangerous (why complicate the rules about object lifetimes?), but presumably there was a need at least for const references, so the standard does set this behavior for them.

[From sbi comment]: Note that the fact that binding it to a const reference enhances a temporary's lifetimes is an exception that's been added deliberately (TTBOMK in order to allow manual optimizations). There wasn't an exception added for non-const references, because binding a temporary to a non-const reference was seen to most likely be a programmer error.

All temporaries do persist until the end of the full-expression. To make use of them, however, you need a trick like you have with ref(). That's legal. There doesn't seem to be a good reason for the extra hoop to jump through, except to remind the programmer that something unusual is going on (namely, a reference parameter whose modifications will be quickly lost).

[Another sbi comment] The reason Stroustrup gives (in D&E) for disallowing the binding of rvalues to non-const references is that, if Alexey's g() would modify the object (which you'd expect from a function taking a non-const reference), it would modify an object that's going to die, so nobody could get at the modified value anyway. He says that this, most likely, is an error.

Brighton answered 8/4, 2012 at 19:26 Comment(0)
G
3

Why would you ever want X& x = getx();? Just use X x = getx(); and rely on RVO.

Gladiate answered 14/10, 2009 at 11:8 Comment(5)
Becouse I want to call g(getx()) rather than g(getx().ref())Publicness
@Alexey, that's not a real reason. If you do that, then you have a logical error soemwhere, because g is going to modify something which you cannot get your hands on anymore.Rhett
@JohannesSchaub-litb maybe he doesn't care.Zimmer
"rely on RVO" except it is not called "RVO".Zimmer
@curiousguy: That is a very accepted term for it. There is absolutely nothing wrong with referring to it as "RVO".Middlebreaker
B
3

The evil workaround involves the 'mutable' keyword. Actually being evil is left as an exercise for the reader. Or see here: http://www.ddj.com/cpp/184403758

Barbirolli answered 14/10, 2009 at 15:2 Comment(0)
M
1

"It is clear that the temporary object is not constant in the sample above, because calls to non-constant functions are permitted. For instance, ref() could modify the temporary object."

In your example getX() does not return a const X so you are able to call ref() in much the same way as you could call X().ref(). You are returning a non const ref and so can call non const methods, what you can't do is assign the ref to a non const reference.

Along with SadSidos comment this makes your three points incorrect.

Malindamalinde answered 14/10, 2009 at 11:41 Comment(0)
C
1

I have a scenario I would like to share where I wish I could do what Alexey is asking. In a Maya C++ plugin, I have to do the following shenanigan in order to get a value into a node attribute:

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);   
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

This is tedious to write, so I wrote the following helper functions:

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &status);
    return returnValue;
}

void operator << (MPlug& plug, MDoubleArray& doubleArray){
    MStatus status;
    MFnDoubleArrayData doubleArrayData;
    MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
    status = plug.setValue(doubleArrayObject);
}

And now I can write the code from the beginning of the post as:

(myNode | attributeName) << myArray;

The problem is it doesn't compile outside of Visual C++, because it's trying to bind the temporary variable returned from the | operator to the MPlug reference of the << operator. I would like it to be a reference because this code is called many times and I'd rather not have MPlug being copied so much. I only need the temporary object to live until the end of the second function.

Well, this is my scenario. Just thought I'd show an example where one would like to do what Alexey describe. I welcome all critiques and suggestions!

Thanks.

Croze answered 22/6, 2013 at 16:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.