20

local lvalue references-to-const and rvalue references can extend the lifetime of temporaries:

const std::string& a = std::string("hello");
std::string&& b = std::string("world");

Does that also work when the initializer is not a simple expression, but uses the conditional operator?

std::string&& c = condition ? std::string("hello") : std::string("world");

What if one of the results is a temporary object, but the other one isn't?

std::string d = "hello";
const std::string& e = condition ? d : std::string("world");

Does C++ mandate the lifetime of the temporary be extended when the condition is false?

The question came up while answering this question about non-copyable objects.

Community
  • 1
  • 1
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • Doesn't the mixed variant need a `std::move` for `d`? – K-ballo Jan 18 '13 at 19:09
  • `d` cannot be bound to an rvalue reference, it's not an rvalue. – Seth Carnegie Jan 18 '13 at 19:09
  • @SethCarnegie Oh right, I just changed the last example to an lvalue reference-to-const. – fredoverflow Jan 18 '13 at 19:11
  • I think `std::string&& e = condition ? d : std::string("world");` should not compile. Because the types of second and third expression are not compatible, or are they? – Nawaz Jan 18 '13 at 19:11
  • 1
    Using ?: is ok. In the mixed case, d is copied. Since both sides of ?: always end up with the same type, the compiler knows to create a single object. – Marc Glisse Jan 18 '13 at 19:13
  • 2
    @SethCarnegie: `d` is an lvalue, but the expression `(true?d:std::string())` is an rvalue that refers to the value stored in `d`. The lvalue will go through an lvalue-to-rvalue conversion (i.e. the value stored in the variable will be read) – David Rodríguez - dribeas Jan 18 '13 at 19:14
  • @MarcGlisse Ah, so `e` would be bound to a copy of `d`? That would make sense. – fredoverflow Jan 18 '13 at 19:14
  • @DavidRodríguez-dribeas Ah, I see. Isn't that kindof bad, since you could accidentally move your variable and use it later in an invalid state? – Seth Carnegie Jan 18 '13 at 19:19
  • @SethCarnegie You would move from a temporary copy of `d`, not `d` iself. – fredoverflow Jan 18 '13 at 19:20
  • @FredOverflow wait, why is a copy made, other than "a copy has to made for this to work"? – Seth Carnegie Jan 18 '13 at 19:24
  • @SethCarnegie I am assuming the language would never silently turn an lvalue into an rvalue when it could be dangerous. Maybe I am assuming too much? :) – fredoverflow Jan 18 '13 at 19:26
  • @FredOverflow: I'm not aware of any syntax other than a conditional operator that actually does apply an lvalue-to-rvalue conversion to an expression with (possibly cv-qualified) class type. – aschepler Jan 18 '13 at 19:32
  • @aschepler Just to be clear, that lvalue-to-rvalue conversion creates a temporary object, right? You won't silently get an rvalue denoting the same non-temporary object as the lvalue. – fredoverflow Jan 18 '13 at 19:37
  • @FredOverflow: Yes, for a (cv-qualified) class type, the lvalue-to-rvalue conversion is defined to copy-initialize a temporary and then use it. Of course, that could usually be elided. (Also, it turns out `static_cast`, `const_cast`, and passing through an ellipsis would also do this.) – aschepler Jan 18 '13 at 19:41
  • 2
    See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#86 – Johannes Schaub - litb Jan 18 '13 at 22:16
  • This discussion is growing too long and hard to follow. However it contains good information which should be integrated into the post. Please do that! – markus Jan 19 '13 at 14:16

2 Answers2

5

Both of those are fine.

§5.16 says (extraordinarily abridged):

2 If either the second or the third operand has type void

Nope.

3 Otherwise, if the second and third operand have different types

Nope.

4 If the second and third operands are glvalues of the same value category

Nope. (In the first, both are prvalues and in the second one is a glvalue and one is a prvalue.)

5 Otherwise, the result is a prvalue

Okay, so both of these result in prvalues. So the binding is fine, but what's the binding to?

6 Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are per- formed on the second and third operands.

Okay, so both are now rvalues if they weren't already.

6 (continued) After those conversions, one of the following shall hold:

The second and third operands have the same type; the result is of that type. If the operands have class type, the result is a prvalue temporary of the result type, which is copy-initialized from either the second operand or the third operand depending on the value of the first operand.

Okay, so it's either std::string(first_operand) or std::string(second_operand).

Regardless, the result of the conditional expression is a new prvalue temporary, and it's that value that's extended by binding to your references.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Neither GCC nor clang make a copy when the expression evaluates to the temporary. Is this a bug in both? – Seth Carnegie Jan 18 '13 at 19:31
  • @SethCarnegie I was about to ask the same question [with this example code](http://ideone.com/WvdflR)... – fredoverflow Jan 18 '13 at 19:34
  • @FredOverflow Oh sorry, clang does move: http://liveworkspace.org/code/2bseJY$1 So it's a bug in GCC? – Seth Carnegie Jan 18 '13 at 19:36
  • Also, what the hell are "glvalues of the same value category"? If they are glvalues, they are glvalues, and hence they are of the same value category. Am I missing something? – fredoverflow Jan 18 '13 at 19:40
  • glvalue = xvalue(&&) or lvalue(&) – Marc Glisse Jan 18 '13 at 19:42
  • @FredOverflow: I believe that to be a bug (either not correctly constructing a new temporary or eliding a copy without checking it's accessible), MSVC also does the right thing. – GManNickG Jan 18 '13 at 19:44
  • I'm not sure I see a problem with GCC 4.7.2's behavior (or I don't understand what you guys are on about). In `const std::string& e = condition ? d : std::string("world");`, if `condition` is false the copy from the temporary `std::string("world")` can be elided due to 12.8/31: "when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move" – Michael Burr Jan 18 '13 at 20:11
  • @MarcGlisse If you're ever bored, [your know where to find us](http://chat.stackoverflow.com/rooms/10/loungec), right? ;) – fredoverflow Jan 18 '13 at 20:26
  • 1
    @Marc: I see - Seth's original original question about this was for a `std::string` (as my comment called out). I see that Fred's linked example does show that GCC doesn't respect that the copy and move were deleted. – Michael Burr Jan 18 '13 at 21:46
3
std::string d = "hello";
const std::string& e = condition ? d : std::string("world");

Does C++ mandate the lifetime of the temporary be extended when the condition is false?

It will be. The conditional is an rvalue expression, and when bound with a const reference the compiler will create an unnamed object and bind the reference to it. What I am not 100% sure is whether the temporary whose lifetime is extended is std::string("world") or whether a copy of it is (conceptually) made (and elided).

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Well, the first case seems to works for a type with a `delete`d copy constructor (and no move constructor), so I *assume* there would be no additional copy in the second case, but I am just guessing. – fredoverflow Jan 18 '13 at 19:21
  • I think 4.1p2 applies, since the conditional operator does an lvalue-to-rvalue conversion: "... if the glvalue has a class type, the conversion copy-initializes a temporary of type `T` from the glvalue and the result of the conversion is a prvalue for the temporary." – aschepler Jan 18 '13 at 19:25
  • @FredOverflow: If `const T& r = T();` compiles for a type that is neither movable nor copyable, the compiler has a bug. – David Rodríguez - dribeas Jan 18 '13 at 19:28
  • I'm pretty sure `const T& r = T();` has *always* worked for non-copyable types in C++98. – fredoverflow Jan 18 '13 at 19:39
  • @FredOverflow: In C++03 12.2p4 describes the process of copying the temporary into an object and binding the reference. I am not seeing such an explicit description in C++11. C++03, 12.2p4: *In that context, the temporary that holds the result of the expression shall persist until the object’s initialization is complete. The object is initialized from a copy of the temporary;* – David Rodríguez - dribeas Jan 18 '13 at 19:46
  • It is really 12.2p5 that applies, and if you look at the example below, it doesn't mention any extra copying. – Marc Glisse Jan 18 '13 at 19:52
  • @MarcGlisse: I am rereading the C++11 standard, and it seems to indicate that the lifetime is extended without copying. The quote is from C++03, and in C++03 the standard explicitly required the copy of the object (i.e. you could not bind a reference to a temporary of a non-copyable type). – David Rodríguez - dribeas Jan 18 '13 at 19:58
  • The example p192 in C++03 doesn't mention any copy, elision, or a 4th temporary. – Marc Glisse Jan 18 '13 at 20:06
  • p4 only applies when the lhs is an object, not a reference. – Marc Glisse Jan 18 '13 at 20:24
  • @MarcGlisse: I am failing to find the appropriate quote, but this seems to be a change from C++98/03 to C++11. GCC, clang and Comeau compilers all reject the code in C++03 and accept it in C++11 mode. I recall that requirement to be explicit in the standard, I just cannot find the quote now. – David Rodríguez - dribeas Jan 18 '13 at 20:38
  • Er, no, g++ and clang both accept `const T& r = T();`, although clang++'s warning agrees with you. Maybe the last paragraph of 8.5.3. – Marc Glisse Jan 18 '13 at 20:51