No it does not.
From my understanding, s should be a dangling reference because its assignment it binds to a return value of std::move
You have looked at the value categories, but not at the types.
The type of "test text"
is char const[10]
. This array reside in const global data.
You then pass it to move, which will return an rvalue reference to the array. The return type of this move cast expression is char const(&&)[10]
, an xvalue to a character array.
Then, this is assigned to a std::string&&
. But this is not a string, but a character array. A prvalue of type std::string
must then be constructed. It is done by calling the constructor std::string::string(char const*)
, since the reference to array will decay into a pointer when passing it around.
Since the reference is bound to a materialized temporary which is a prvalue, lifetime extension of the reference apply.
The std::move
is completely irrelevant and does absolutely nothing in this case.
So in the end your code is functionally equivalent to this:
std::string&& s = std::string{"test text"};
std::cout << s << std::endl;
The answer would be difference if you would have used a std::string
literal:
// `"test text"s` is a `std::string` prvalue
std::string&& s = std::move("test text"s);
std::cout << s << std::endl;
In this case, you have a prvalue that you send to std::move
, which return an xvalue of type std::string
, so std::string&&
.
In this case, no temporary is materialized from the xvalue
return by std::move
, and the reference simply bind to the result of std::move
. No extension is applied here.
This code would be UB, like the example you posted.