11

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

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 3
    You have incorrectly been told... const-ness is orthogonal to temporary-ness (*lvalue* vs *rvalue*-ness) – David Rodríguez - dribeas Jun 24 '11 at 10:09
  • 1
    @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. – Lightness Races in Orbit Jun 24 '11 at 10:11
  • 2
    Only temporaries of non user defined class type are implicitly non-modifiable: `std::vector().resize( 100 );` is perfectly valid, creates a temporary and modifies it (without the need for a cast as in your example). – David Rodríguez - dribeas Jun 24 '11 at 10:21
  • @David: Read my answer, which contains a standard quote. That member functions may modify the object is, essentially, an exception to the rule. – Lightness Races in Orbit Jun 24 '11 at 10:23
  • @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. – Lightness Races in Orbit Jun 24 '11 at 10:23
  • @David It's a `static_cast` and would not be able to remove const-ness. – Luc Danton Jun 24 '11 at 10:28
  • `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&`. – Steve Jessop Jun 24 '11 at 10:45
  • @Steve: That's the whole point :) – Lightness Races in Orbit Jun 24 '11 at 10:47
  • @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. – Steve Jessop Jun 24 '11 at 10:49
  • @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). – Lightness Races in Orbit Jun 24 '11 at 10:56
  • @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. – Steve Jessop Jun 24 '11 at 11:38
  • @Steve: I expanded my answer to clarify that point a while back. :) – Lightness Races in Orbit Jun 24 '11 at 11:50
  • It's clearer nowadays that this was nonsense, since move semantics mutates temporaries all the time. – Lightness Races in Orbit Jun 25 '14 at 15:55

2 Answers2

20

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.

Community
  • 1
  • 1
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 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. :) – Lightness Races in Orbit Jun 24 '11 at 10:11
  • So `std::string()` is not an rvalue and thus a non-const variable? – StackedCrooked Jun 24 '11 at 10:11
  • @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). – Lightness Races in Orbit Jun 24 '11 at 10:12
  • @Stacked: `std::string()` *is* an rvalue that denotes a *non-const* temporary. It is *not* a variable, since variables always have names in C++. – fredoverflow Jun 24 '11 at 10:25
  • @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. – David Rodríguez - dribeas Jun 24 '11 at 10:26
  • @David: You can bind a non-const rvalue reference to `std::string()`. – fredoverflow Jun 24 '11 at 10:33
  • @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*. – David Rodríguez - dribeas Jun 24 '11 at 10:38
  • @David: Right, the question is explicitly about C++03. I removed the rvalue references from my answer. – fredoverflow Jun 24 '11 at 10:45
  • 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) – Johannes Schaub - litb Jun 24 '11 at 19:00
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!)

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    +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. – David Rodríguez - dribeas Jun 24 '11 at 10:23
  • @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. – Lightness Races in Orbit Jun 24 '11 at 10:25
  • 1
    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. – David Rodríguez - dribeas Jun 24 '11 at 10:29
  • @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 [..]". – Lightness Races in Orbit Jun 24 '11 at 10:33
  • 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. – David Rodríguez - dribeas Jun 24 '11 at 10:41
  • @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. – Lightness Races in Orbit Jun 24 '11 at 10:47
  • 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. – Steve Jessop Jun 24 '11 at 10:53
  • @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. – David Rodríguez - dribeas Jun 24 '11 at 10:58
  • @Steve: Aha, this is true. We're having a discussion about what ways there are to modify the object [here](http://stackoverflow.com/questions/6466624/how-can-you-modify-an-object-without-calling-member-functions). – Lightness Races in Orbit Jun 24 '11 at 10:59
  • @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. – Lightness Races in Orbit Jun 24 '11 at 11:00
  • 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? – Lightness Races in Orbit Jun 24 '11 at 11:38
  • @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. – David Rodríguez - dribeas Jun 24 '11 at 12:27
  • @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). – Lightness Races in Orbit Jun 24 '11 at 12:42
  • @Tomalak: `static_cast( 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) – David Rodríguez - dribeas Jun 24 '11 at 12:52
  • @DavidRodríguez-dribeas (...) "_is perfectly valid and does not use a member function_" I disagree. I should be valid, but it isn't. – curiousguy Dec 21 '11 at 12:26
  • @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. – curiousguy Dec 21 '11 at 12:29
  • @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. – David Rodríguez - dribeas Dec 22 '11 at 09:09