简体   繁体   中英

Safety of auto when std::move ing from a returned reference of a value

I need some reassurance about whenever, assigning or list initializing an auto typed named variable, with

  • A a std::move ()ed returned reference to a variable
  • B a returned reference to a variable

where after the expression, the origin gets out of scope, is A safe / B unsafe. Example code:

#include <iostream>
#include <string>
#include <deque>
#include <utility>

int main() {
    std::deque<std::string> container {"foo"};
    auto elementB = container.front(); //B I assume this is unsafe
    auto elementA = std::move(container.front());//A I assume this is safe
    container.pop_front();

    std::cout << "A: " << elementA << " B: " << elementB  << "\n";
}

As far as I understand expression B generates a lvalue right of the assignment and so the type of elementB is a lvalue reference aka std::string& and so that one would be unsafe.

Also the output of "A: foo B: " when executing the code suggests that. ( https://ideone.com/wKKbdK ) Update: Sorry I forgot that I moved it, so I changed the order and now the output is as excepted, sorry.

However the much more troublesome thing where I am unsure is expression A: After the std::move I assume I got an xvalue which is both a rvalue and a glvalue, so I am not sure whats the standardized behavior if any, for the type deduction of elementA .

Because from lvalues I almost sure its UB, and lvalues are glvalues, and xvalues are part of them, then the type would of elementA would be std::string&& , which would be unsafe right? (unless the exception for const&& AFAIK)

So to summarize: Is the usage of elementA safe standardized behaviour and what will be its type?

Is the usage of elementA safe standardized behaviour?

Yes.

... and what will be its type?

It's type will be std::string . auto type deduction works like template type deduction, and that includes removing the "referenceness" of a reference. The fact that std::move(container.front()) returns an xvalue doesn't really change much here. It is an "expiring" value, you can either (a) move-construct a new object (as you currently do) (b) bind it to a const -qualified reference or (c) bind it to an rvalue-reference. Here, (b) and (c) both work but don't make much sense, as they obscure the fact that nothing is moved-from (thanks to @MM for correcting me here). Example:

auto elementA = std::move(container.front());
// Error, can't bind to non-const reference:
// auto& doesntWork = std::move(container.front());
auto&& thisWorks = std::move(container.front());
const auto& thisWorksToo = std::move(container.front());

Note that as @MM pointed out in the comments, the last two references will be dangling once container.pop_front(); is encountered.

Also note that the deduction of elementB as std::string doesn't help the fact that you are dereferencing a moved-from object (return by container.front() ), which you should avoid.

Your code is fine, because both elementA and elementB will be deduced as string , not reference ( string& or string&& ).

Given auto elementA , the reference part of the initializer will be ignored. If you want elementA or elementB to be reference, you have to specify explicitly, eg auto&& elementA or auto& elementB .

You might be confusing value category with types; they're independent. The value category of the initializer, ie it's an lvalue or rvalue, won't affect type deduction . In your code, given auto elementA , elementA will be of type std::string , given auto& elementA , its type will be std::string& , given auto&& elementA , its type will be std::string&& .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM