3

Looking at the following code:

#include<string>
#include<iostream>
using T = std::string;


void baz(T& v)       { std::cout << v << std::endl; }
void boo(const T& v) { std::cout << v << std::endl; }

int main() {
        baz( T("Passing an rvalue to be assigned to a lvalue&") ); //<--- This call is invalid
        boo( T("Passing an rvalue to be assigned to a constant lvalue&") );
}

The standard tells us the first function call is invalid for obvious reason but the second function call isn't invalid because it can be interpreted as "Generate a variable bound to the scope of the function boo, which holds an lvalue equal to the rvalue passed to the function". At least as far as my understanding of the standard goes.

However, adding an auxiliary functions such as:

constexpr T &to_lvalue(T &&value) noexcept { return value; }

And calling the function baz using it to wrap parameters:

baz( to_lvalue( T("Passing a rvalue which will be transformed into an lvalue reference bound to the scope of baz") ) );

Is considered perfectly valid code and (to my understanding) it basically results in the exact same thing, namely the construction of an lvalue that is bound to the scope of baz

So why wouldn't the compile simply generate this code for you when calling baz with an rvalue ? It ads no runtime overhead and the behavior seems to be well defined. Is there something that I'm missing here ? Or is the standard defined in such a way in order not to "mistakenly" use a function that is expected to mutate a value ?

George
  • 3,521
  • 4
  • 30
  • 75
  • 8
    Your perfectly valid code is UB. The temporary passed to `to_lvalue` expires after `to_lvalue` returns, leaving you with a dangling reference. – nwp Aug 29 '17 at 12:22
  • 3
    `to_lvalue()` returns a reference to a temporary that goes out of scope and gets destroyed when `to_lvalue()` returns. As such, this is undefined behavior. – Sam Varshavchik Aug 29 '17 at 12:24
  • 2
    Isn't the life of a temporary extended until the end of the statement? – rustyx Aug 29 '17 at 12:25
  • @nwp So is the implementation in gcc/clang broken in this case ? Because the code seems to produce the expected output and there's not warnings when compiling it – George Aug 29 '17 at 12:25
  • 2
    *seems to produce the expected output* That's what is fun with UB. It might work, it might not, it might work some of the time, and it might also start breaking when you change something totally unrelated. – Borgleader Aug 29 '17 at 12:27
  • It is not broken. Doing exactly what you wanted it allowed behavior for undefined behavior. You can use Asan and UBsan in order to get an error or use -O3 to try to make it produce garbage. The idea behind UB is that the compiler just doesn't care. – nwp Aug 29 '17 at 12:29
  • clang++ -std=c++1z -O3 -fsanitize=undefined main.cpp Still results in the same output, same with gcc – George Aug 29 '17 at 12:32
  • @nwp there are rare cases when [it does care](http://eel.is/c++draft/expr.const#2.6) – W.F. Aug 29 '17 at 12:34
  • @RustyX that was my understanding as well. Would want to find somthing in the standard to quote though. FWI testing on MSVC 2015 it appears to be the case, saving to a `T&` temp then calling `baz` on the next statement results in a destructed object (MSVC set its size to zero, but more creative code could like induce a crash) – Fire Lancer Aug 29 '17 at 12:35
  • 4
    In this very specific case I believe you are okay because `T("Passing an rvalue to be assigned to a lvalue&")` lives to the end of the expression. If you make it two steps [it explodes](http://coliru.stacked-crooked.com/a/8381fa1dee248807). Making a `to_lvalue` would just lead to more problems IMHO. A compiler error is a lot easier to fix then random crashes due to undiagnosed UB. – NathanOliver Aug 29 '17 at 12:36
  • @NathanOliver I modified your code a bit to make it more readable and force an error: http://coliru.stacked-crooked.com/a/18edb30f599986f9 I managed to get a runtime warning but it still seems to run and produce the expected output, despite the object being destroyed before the function call o.O – George Aug 29 '17 at 12:46
  • However, it should be mentioned that using the function the way I showcased: http://coliru.stacked-crooked.com/a/e32a119c75f2e881 , whilst still resulting in a warnning, does seem to bound the variable to baz's scope, since the destructor is invoked only when baz exists – George Aug 29 '17 at 12:48
  • What part of "undefined behavior means anything can happen" is unclear? "Anything can happen" means that: 1) the compiler can generate the code you intend it to generate, 2) the compiler fails to compile the code at all, 3) the compiler generates completely incorrect code, or 4) all life as you know it coming to an end, and every molecule exploding at the speed of light, just to name some of the possibilities. – Sam Varshavchik Aug 29 '17 at 12:52
  • @SamVarshavchik If you believe that the value is not garunteed to live until the end of the statement, then make that an answer, ideally with a reference to the standard. – Fire Lancer Aug 29 '17 at 12:58
  • @George It's hard to say. On the one hand you have that temporary's live to the end of the expression, on the other you have that when the reference bound to them goes out of scope the temporary is destroyed. I don't know if the latter trumps the former in this case and that is what we need to know in order to know if it is UB or not. – NathanOliver Aug 29 '17 at 13:09
  • @NathanOliver I guess neither [gcc](https://wandbox.org/permlink/FcNSWMllJkpVL3xQ) nor [clang](https://wandbox.org/permlink/n7dN51puXHK7btji) think the code has UB – W.F. Aug 29 '17 at 13:37
  • @W.F. Yes, the code compiles on all platforms. That still doesn't mean it isn't undefined behavior. We're really only going to be able to know for sure with a [tag:language-lawyer] response. I'm inclined to believe this case is OK because of the clause the temporaries live to end of the full expression. – NathanOliver Aug 29 '17 at 13:42
  • @NathanOliver according to [[expr.const#2.6](http://eel.is/c++draft/expr.const#2.6)] my variation of the code from the previous comment should intentionally provoke compiler to test whether the temporary like that has extended lifetime until the end of expression. Or am I missing something...? – W.F. Aug 29 '17 at 13:50
  • @W.F. Thanks. That is interesting. I'm not sure if the reference conversion is considered an operation or an expression. If it is the latter then it should be OK. – NathanOliver Aug 29 '17 at 13:57

0 Answers0