Search code examples
c++c++11c++14autolvalue-to-rvalue

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?


Solution

  • 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 @M.M 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 @M.M 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.