If temporaries are implicitly non-modifiable, how does this work?
Asked Answered
A

2

12

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

Algia answered 24/6, 2011 at 10:0 Comment(13)
You have incorrectly been told... const-ness is orthogonal to temporary-ness (lvalue vs rvalue-ness)Brouhaha
@David: I should have been more careful with the question title, really. I really did mean "implicitly non-modifiable" rather than specifically "const". And as it turns out, to a point, this is exactly what they are.Algia
Only temporaries of non user defined class type are implicitly non-modifiable: std::vector<int>().resize( 100 ); is perfectly valid, creates a temporary and modifies it (without the need for a cast as in your example).Brouhaha
@David: Read my answer, which contains a standard quote. That member functions may modify the object is, essentially, an exception to the rule.Algia
@David: And the cast is only for .str(), because op+ returns ostream. It's a bit of a red herring here as it has nothing to do with the question specifically.Algia
@David It's a static_cast and would not be able to remove const-ness.Danettedaney
operator<<(int) is a member function of basic_ostream anyway, so whatever special rule you think applies to vector::resize, it would in any case also apply to your code. The cast is just a downcast back to stringstream, since operator<< returns basic_ostream&.Schalles
@Steve: That's the whole point :)Algia
@Tomalak: in that case I don't quite see why you're asking "how does this work?" if you already know the answer - because member functions may modify the object. But anyway, I don't think it's a special case that member functions are allowed to modify the object, I think it's a special case that member functions are allowed to be called on the temporary even though normally it can't be bound to non-const reference/pointer. As far as I know, taking a const reference to a temporary, casting away const and modifying it, is legal, just make sure the reference doesn't outlive the temporary.Schalles
@Steve: I didn't already know the answer. I posed the question, found out the answer and posted it. And as for the last bit of your comment, 3.10/10 would seem to directly contradict you: the temporary simply may not be modified except under certain circumstances (including a member function call).Algia
@Tomalak: as I commented to your answer, I think that you're reading 3.10/10 incorrectly. It says you can't use an rvalue to modify the object (except blah blah). It doesn't say you can't modify the object (except blah blah). Similarly one can say that you can't use a pointer-to-const to modify an object - it doesn't follow that you couldn't necessarily modify the same object using a pointer-to-non-const. rvalue or lvalue is a property of an expression, not a property of the object to which that expression refers, so rvalue != temporary.Schalles
@Steve: I expanded my answer to clarify that point a while back. :)Algia
It's clearer nowadays that this was nonsense, since move semantics mutates temporaries all the time.Algia
F
21

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.

Fourcycle answered 24/6, 2011 at 10:2 Comment(9)
See my answer. They're not const per-se, but they are also non-modifiable in all but some specified cases. Those specified cases include member function calls which probably covers the majority of use cases, but still. :)Algia
So std::string() is not an rvalue and thus a non-const variable?Eryneryngo
@StackedCrooked: It's an rvalue; it's not const, but it's also not modifiable (except under certain conditions, like calling a member function on it).Algia
@Stacked: std::string() is an rvalue that denotes a non-const temporary. It is not a variable, since variables always have names in C++.Fourcycle
@StackedCrooked: A different point of view that helps me think on that: you cannot bind a non-const reference to std::string(), but you can call any member method directly on it. Another different point of view is that the language has a clause (somewhere, you can search for it) that basically says that all lvalues are objects, and that some rvalues (rvalues of class types, for example) can also be objects. You can call a method on any object.Brouhaha
@David: You can bind a non-const rvalue reference to std::string().Fourcycle
@FredOverflow: Not in my compiler, not in my standard. :) I think that at least until the new standard is actually approved, we should be explicit when referring to c++0x features as rvalue-references and the whole new family of beasts. At any rate, that is what I do, so the comment stands as it was with an implicit in the current standard.Brouhaha
@David: Right, the question is explicitly about C++03. I removed the rvalue references from my answer.Fourcycle
And there can also be temporary objects that are denoted by lvalues. Consider: try { throw 0; } catch(int &r) { r = 1; } this writes to a temporary object of type int (note to the non-inclined: The/A temporary is not the value 0, but the internal object that is initialized from it)Pendent
A
8

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!)

Algia answered 24/6, 2011 at 10:7 Comment(17)
+1, and as an additional note, the usual semantics for a show() method (as per the original question), would be those of a constant method (show() does not seem to entail modify but rather only read), so even if temporaries were const, if the method is also const it should work.Brouhaha
@David: Indeed; I hadn't even noticed how poor the example is, since show() ought to be const anyway. :) I've edited to expand on that slightly.Algia
BTW: Another approach to the same problem is considering that you can call member methods on objects, and 3.10/2 states that An lvalue refers to an object or function. Some rvalue expressions—those of class or cv-qualified class type— also refer to objects. Then the implication is that you can call methods for lvalues and rvalues of class or cv-qualified class type.Brouhaha
@David: You still need to come back to 3.10/10 to make sense of that, since "An lvalue for an object is necessary in order to modify the object, except [..]".Algia
If you aggregate the two rules into one, you will note that the meaning is that An object is required in order to be modified. That is, in both cases there is a common denominator lvalue or class type rvalue, that is the requisite for mutability in 3.10/10, or the definition of object in 3.10/2, which is what I failed to show in the last comment.Brouhaha
@David: No. "An lvalue is required to be modified" is not equivalent to "An object is required in order to be modified", simply because of what you quoted: both lvalues and rvalues may refer to objects.Algia
That quote just says that an rvalue of class type can only be used to modify the object under certain circumstances. It doesn't say anything one way or the other about the validity of using an rvalue of class type to obtain an lvalue, and then using the lvalue to modify the object.Schalles
@Tomalak: Really? (BTW, my last comment is missing a non-const somewhere, const disables mutability). Enumerate the conditions by which an expression represents an object (lvalue, rvalue of class type), and the conditions required to modify it (lvalue, rvalue of class type). Note that through a member function is just one example. struct test { int x; }; test foo() { return test(); } int main() { foo().x = 5; } is perfectly valid and does not use a member function. The standard is written in a legal language, but don't let that confuse you, the actual concepts are in most cases simple.Brouhaha
@Steve: Aha, this is true. We're having a discussion about what ways there are to modify the object here.Algia
@David: I think I was pointing out an error on your part with merging the two English clauses; I'm not yet sure which way I'm leaning on the actual meaning of what the standard's trying to say. It may be that Friday morning is getting the best of me.Algia
Hmm, regarding the code sample in the footnote... global is an lvalue, but isn't it converted to an rvalue for use? And aren't I then trying to modify the temporary through an rvalue again?Algia
@Tomalak: Regarding the convoluted example, I am not sure of what you were trying to test... What you are actually testing (and focusing only on the call to memset) is: global is an lvalue expression, when used as with memset that takes the argument by value, an lvalue-to-rvalue conversion is performed (the value of the variable is read for the copy). Internally, memset dereferences the pointer, and pointer dereferencing (operator*) is an lvalue expression, which is then used to modify the object pointed to. I don't see the point of all the rest of the code in the test.Brouhaha
@David: It's a way to modify the temporary through an lvalue, rather than an rvalue (and thus we are not limited to using member function calls).Algia
@Tomalak: static_cast<standard_layout&>( standard_layout{} ) gives you exactly the same with much less typing, and in a safer way (i.e. you will not leave a dangling pointer after completion). At the same time, the whole idea of move operations deal with modifying an rvalue (well, what was an rvalue in C++03). There is no need to store a pointer elsewhere, as you can call memcpy on the address of rvalue-reference: void foo( standard_layout && x ) { memcpy( &x, 0, sizeof( standard_layout ) ); } (I have not tried, and have not played with C++0x, I might be wrong here)Brouhaha
@DavidRodríguez-dribeas (...) "is perfectly valid and does not use a member function" I disagree. I should be valid, but it isn't.Remanent
@DavidRodríguez-dribeas "modifying an rvalue (well, what was an rvalue in C++03)" Unless I'm missing something, what was an rvalue is an rvalue.Remanent
@curiousguy: I should be careful not to comment when in a hurry, the code sample should be: struct test { int x; test& self() { return *this; } }; test foo(); foo().self().x = 5; the temporary is modified through direct assignment, the modificiation itself does not need to be performed in a member function.Brouhaha

© 2022 - 2024 — McMap. All rights reserved.