1

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?

Superlokkus
  • 4,731
  • 1
  • 25
  • 57

2 Answers2

4

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.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • Corrected the move from, to get to the essential parts (sorry I overlooked it). Could you explain the value category mumbo jumbo, thats the biggest issue I got in understanding here. – Superlokkus Jun 18 '19 at 08:37
  • Bu t I guess usual `typename` template parameters have slightly other rules, since otherwise forwarding references with reference allusion wouldn't work right_ – Superlokkus Jun 18 '19 at 09:02
  • Can you give an example? – lubgr Jun 18 '19 at 09:03
  • `template void bar(T&& i);` i.e. https://blog.petrzemek.net/2016/09/17/universal-vs-forwarding-references-in-cpp/ i.e. https://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c – Superlokkus Jun 18 '19 at 09:05
  • I don't see where this should differ. `auto&& x = someThing();` deduces the same type as f(someThing())` where `f` is `template void f(T&&)`. – lubgr Jun 18 '19 at 09:09
  • In the example of the second link with: `template void func(T&& t) { }` `double d = 3.14; func(d); // d is an lvalue; T deduced to double&` With my point beeing that there the referenceness of the type is not stripped away in template type deduction. – Superlokkus Jun 18 '19 at 09:16
  • ... and so is `double x = 3.14; auto&& d = x;` `d` is of type `double&`. – lubgr Jun 18 '19 at 09:21
  • I feared that then `auto f =x;` would be of type `double&` too. – Superlokkus Jun 18 '19 at 09:25
  • 1
    Well, your fear luckily wasn't justified ;) – lubgr Jun 18 '19 at 09:26
  • 2
    Note that your references labelled as `Works` become dangling after `container.pop_front()` and the `cout` line therefore causes undefined behaviour – M.M Jun 18 '19 at 09:29
  • You say something about "extend the lifetime of the xvalue" but that makes no sense; xvalues are expressions and expressions don't have lifetimes. Binding a reference to a member of a deque does not do anything to that member's lifetime. – M.M Jun 18 '19 at 09:31
  • 1
    @M.M Thanks, that's indeed bogus. I'll try to correct it. – lubgr Jun 18 '19 at 09:37
  • I guess the "An rvalue may be used to initialize an rvalue reference, in which case the lifetime of the object identified by the rvalue is extended until the scope of the reference ends." rule from https://en.cppreference.com/w/cpp/language/value_category only applies to temporaries, and since the element of the container aren't temporaries. dangeling ref.? – Superlokkus Jun 18 '19 at 09:44
  • @Superlokkus yes, it should say prvalue there – M.M Jun 18 '19 at 11:17
  • BTW I could also finally get my head around the other confusion: I did not know that the template parameter deduction differs on how the parameter is used/declared. Given `template void funcA(T&& t) { } template void funcB(T t) { } double d = 3.14;` `funcA(d)` would be equivalent to ` funcA(d)` but `funcB(d)` would be equivalent to `funcB(d)` – Superlokkus Jun 18 '19 at 12:35
2

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, e.g. auto&& elementA or auto& elementB.

You might be confusing value category with types; they're independent. The value category of the initializer, i.e. 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&&.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • If its safe, why am I getting "A: foo B: " as output? – Superlokkus Jun 18 '19 at 08:30
  • @Superlokkus Because `container.front()` has been move-initialized to `elementA`, and the moved `std::string` became empty. Then `elementB` is initialized from an empty `std::string`. – songyuanyao Jun 18 '19 at 08:33
  • Corrected the move from, to get to the essential parts (sorry I overlooked it). Could you explain the value category mumbo jumbo, thats the biggest issue I got in understanding here. – Superlokkus Jun 18 '19 at 08:36
  • 1
    @Superlokkus Value category doesn't matter here, whether the initializer is an lvalue or rvalue, the type will be `std::string`. Value category won't affect type deduction. – songyuanyao Jun 18 '19 at 08:45