19

I would like to check my understanding and conclusions on this matter.


On IRC, it was asked:

Is it acceptable to const_cast a const reference that's bound to a temporary object?

Translating: he has a ref-to-const bound to a temporary, and he wants to cast away its const-ness to modify it.

My response was that I'd asked a similar question previously, where the consensus seemed to be that temporaries themselves are not inherently const, and thus that you can cast off the const-ness of a reference you have to them, and modify them through the result. And, as long as that original ref-to-const still exists, this won't affect the temporary's lifetime.

That is:

int main()
{
   const int& x = int(3);

   int& y = const_cast<int&>(x);
   y = 4;

   cout << x;
}
// Output: 4
// ^ Legal and safe

Am I right?


(Of course, whether or not such code is actually advisable is another matter entirely!)

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • @Mark: Oh, a literal might have been a bad choice. I have now made it a non-literal for this question. – Lightness Races in Orbit Nov 23 '11 at 15:57
  • I was about to say that there were no temporaries in that code. Fortunately, I refreshed the page before doing it :) – Gorpik Nov 23 '11 at 15:59
  • afaik (but I cant find it in the stadnard) it says something like "when it was declared as const, then you may not cast constness away"... so the question is, is a literal declared as const? I dont think so, since it results in a temporary object, so is that declared as const? I have no idea, but my guts say: no – PlasmaHH Nov 23 '11 at 16:00
  • @MarkB: Though `2.14.2` doesn't actually prohibit or undefine modifying integer literals in the way that `2.14.5/12` does for string literals. Curious! – Lightness Races in Orbit Nov 23 '11 at 16:00
  • Relevant, but not a duplicate: http://stackoverflow.com/questions/3484233/const-method-that-modifies-this-without-const-cast – GManNickG Nov 23 '11 at 17:11
  • @TomalakGeret'kal That distinction comes from C: a string literal defines an object (which is unmodifiable, and in C++, `const`); other literals don't require objects (so don't have cv-qualifiers). – James Kanze Nov 23 '11 at 17:43
  • @James: And there they're called "constants", right? We're on a bit of a tangent now, but I guess we could say that the "spirit" of the standard and its predecessors implies `int` literals to be unmodifiable, even if I can't find anything that states so explicitly. – Lightness Races in Orbit Nov 23 '11 at 18:00
  • @TomalakGeret'kal In the standard, they're called literals, but we commonly call them constants. Except that literals are rvalues, and rvalues of non-class type aren't objects, so to bind a reference to them, the compiler has to create a temporary object. Except for string literals, which even in C were objects. The result is somewhat anomalous: `3` has type `int`, but `"3"` has type `char const[2]` (with a `const`). – James Kanze Nov 23 '11 at 18:10
  • @JamesKanze: C++11 footnote 21: `The term “literal” generally designates, in this International Standard, those tokens that are called “constants” in ISO C.` By "there" I was talking about ISO C. – Lightness Races in Orbit Nov 23 '11 at 18:19

2 Answers2

9

No.

First, as far as I can tell, whether it is a literal or not is irrelevant. Rvalues of non-class types always have non-cv qualified types (§3.10/9), however, in §8.5.3 (initialization of a reference), we have:

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

[...]

--

Otherwise, a temporary of type “cv1 T1” is created and initialized from the initializer expression using the rules for a non-reference copy initialization (8.5). The reference is then bound to the temporary. If T1 is reference-related to T2, cv1 must be the same cv-qualification as, or greater cvqualification than, cv2; otherwise, the program is ill-formed.

(All of the preceding points concern either lvalues or class types.)

In our case, we have:

int const& x = ...;

So cv1 T1 is int const, and the temporary object we create has type int const. This is a top level const (on the object), so any attempt to modify it is undefined behavior.

At least, that's my interpretation. I wish the standard were a bit clearer about this.

Community
  • 1
  • 1
James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Doesn't that just make 3.10/9 and 8.5.3 mutually contradictory? – Lightness Races in Orbit Nov 23 '11 at 18:02
  • 2
    @TomalakGeret'kal Not really. §3.10/9 talks about rvalues and lvalues. An expression like `3` or `int(3)` is an rvalue. §8.5.3 talks about how to initialize a reference; references are never rvalues, and must refer to an object. So §8.5.3 says that the compiler creates a temporary object, initialized with the rvalue, and binds the reference to that. You don't get a reference to an integer literal, because such things don't exist; you get a reference to an otherwise invisible, unnamed object, which has the type of the reference (and not of the initializer). – James Kanze Nov 23 '11 at 18:14
  • Ah! I see................. Interesting. Then I agree with your conclusion, and I suppose this means that extending the lifetime of temporaries has the unfortunate corollary that we cannot modify them. – Lightness Races in Orbit Nov 23 '11 at 18:17
  • @TomalakGeret'kal I think so. But you can always wrap them in a class, and make them mutable. – James Kanze Nov 23 '11 at 18:23
  • It certainly used to be the case that literal numbers in Fortran were compiled into references; code could even assign to them and modify the meaning of the numbers throughout the whole program! This was found to be an extremely bad idea… – Donal Fellows Nov 23 '11 at 21:21
  • 1
    The most interesting thing I find in @James's comment is this : *you get a reference to an otherwise invisible, unnamed object, **which has the type of the reference (and not of the initializer)***. Awesome. After this, everything is pretty much straightforward and obvious! – Nawaz Nov 24 '11 at 08:45
  • @JamesKanze: I've one question. If I write `int const & i = f();` where `f` is this : `int f() { return 100;}`, then what would be the *type* of object which `i` is referring to? Is it `int` or `int const`? Please explain the rationale as well. I asked this, because there is no integer literal involved in `int const &i = f()`. – Nawaz Nov 24 '11 at 08:49
  • 1
    @Nawaz As I read the standard, the type of the temporary object is still `int const`. The return value is an rvalue of non-class type (so cv-qualifiers are ignored---there is no `int const f()`). So we fall through to the last point in §8.5.3 (quoted elsewhere). The essential point here is that non-class types are handled differently than class types, and that for non-class types, the temporary has the type of the reference ("_cv1_ T1"). (For class types, the const-ness of the rvalue expression is significant.) – James Kanze Nov 24 '11 at 09:56
  • 1
    @DonalFellows In the original Fortran, yes. Something like `f(3)` could actually call `f` with a value of 4. "Standard" Fortran required the generated code to reinitialize the argument each time, so that `f(3)` really did always call `f` with 3. – James Kanze Nov 24 '11 at 09:59
  • In C++17 it's been made very clear that the temporary is `const` (for class types as well as built-in) – M.M Feb 17 '19 at 23:29
  • @M.M. Temporaries are not implicitly const objects. One can bind a temorary to a rvalue reference and then modify it, or call a non-const member function on one directly. Making temporaries const would break move semantics, and make quite a lot of noise in the process. – n. m. could be an AI Dec 04 '19 at 18:14
1

The answer depends on how the temporary is created and how the reference is initialized.

If you explicitly created the temporary yourself as an object of non-const type and the situation guarantees that the const-reference is attached specifically to the temporary you created, then you can safely cast away the constness of the reference and modify the object.

On the other hand, if the temporary was implicitly created for you by the compiler, then the temporary itself will be const. In that case modifying that temporary leads to UB.

Unfortunately, the C++ language standard by itself does not seem to seem to guarantee any situations where the first initialization approach is necessarily taken. In any context the compiler is allowed to introduce an extra temporary copy of your original temporary. The new temporary will be const (as stated above) and therefore non-modifiable. Whether this happens or not is implementation-defined, as stated in 8.5.3/5.

So, in general case the answer is no, while an implementation-specific answer might be different.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • I can't find anything in 8.5.3/5 that indicates that any temporary on the RHS _wouldn't_ fall through to the clause James quoted. Can you demonstrate that the scenario is different when I "explicitly create the temporary [myself]"? – Lightness Races in Orbit Nov 23 '11 at 18:30
  • 1
    @Tomalak Geret'kal: Well, for a class type `T`, if I just do `const T& r = T()`, I have a chance that the reference will get attached directly to my `T()` (says 8.5.3/5), which is not const. In that case it is perfectly legal to cast away the constness and modify the object. – AnT stands with Russia Nov 23 '11 at 18:54
  • 8.5.3/5 is huge and, as I said, I can't find the deductive steps that you're using. – Lightness Races in Orbit Nov 23 '11 at 19:38
  • 1
    @Tomalak Geret'kal: I'm talking about the part that begins with "- Otherwise, the reference shall be to a non-volatile const type...". The very next bullet says "- If the initializer expression is an rvalue ... the reference is bound in one of the following ways (the choice is implementation-defined)". That bit is the one that applies here and that is the implementation-defined behavior I'm referring to. – AnT stands with Russia Nov 23 '11 at 19:48
  • @AndreyT When doing `const T& r = T()`, no compiler, ever, made a copy. There is no technical reason for making a copy, and no technical justification for allowing a copy to be made. The guys who wrote the part "_in one of the following ways (the choice is implementation-defined)_ (...)" just didn't understood C++. – curiousguy Dec 21 '11 at 12:17
  • @curiousguy: It is completely irrelevant if any compiler ever made any copy here. What's relevant is that the language specification explicitly allows a copy to be made. Obviously, this provision in the standard was not intended for situations like `const T& r = T()`, so it is not surprising that compilers don't make copies in such cases (why would they?). In more convoluted cases like `const T& r = some_bool ? T() : 0` (assuming `T` is convertible from `0`) some compilers were known to make seemingly unnecessary copies. – AnT stands with Russia Dec 24 '11 at 20:31
  • In C++17, `const T& r = T();` guarantees that there is only one temporary and it has type `const T` (applies to built-in and class types)... not sure if/when that changed from C++11 – M.M Feb 17 '19 at 22:04