When not to use `auto&&`?
Asked Answered
S

4

18
  auto&& mytup = std::make_tuple(9,1,"hello");
  std::get<0>(mytup) = 42;
  cout << std::get<0>(mytup) << endl;
  1. Is there a copy/move involved (without RVO) when returning from make_tuple?
  2. Is it causing undefined behavior?
  3. I can both read write the universal reference. Can auto&& var = func() be used always instead of auto var = func() so that there is no copy/move?
Sublapsarianism answered 13/2, 2013 at 8:14 Comment(5)
A reference is a reference.Motheaten
regarding auto&& var = func() , #13619006Helman
@Helman The link is a different case where it is returning a local variable as a reference. The bug is is inside the function not where it is called.Sublapsarianism
@billz: The linked question has no relevance to this one, it's like saying that const int & foo() { return 42; } int i = foo(); and int foo() { return 42; } const int & i = foo(); are the same situations (they are not).Hundredpercenter
@6502 Yeah, indeed. People that cannot handle references in C++03 won't magically get their lives made easier by C++11, but well.Chauvin
L
8
  1. Yes. Any return from a function that does not return a reference type may involve a copy/move. Eliding that is what RVO is about. The object that your reference is bound to needs to be initialized somehow.

  2. No. why should it? The lifetime of a temporary/prvalue bound to a reference is determined by the scope of the reference.

  3. If func() does not return a reference type, there should not be any difference in efficiency (nor behaviour)

between

auto&& var = func();

and

auto var = func();

In both cases an object with lifetime to the end of the containing block is constructed. In one case it has its own name, in the other it is named via a reference. In both cases the name can be used as an lvalue. RVO can be equally well applied in either case.

Some compilers might optimize better for a local object than for a reference, even though in the current case the reference-to-temporary is really no different from a local object.

If func() might return a reference, things are much different - in that case you must decide whether you want to copy/move or not.

Laural answered 13/2, 2013 at 8:43 Comment(4)
If func happened to return an lvalue like Foo& func(), auto&& version will not copy while auto will copySublapsarianism
@balki: But we were discussing a value-returning function here. I now made the distinction explicit.Laural
"The lifetime of a temporary/prvalue bound to a reference is determined by the scope of the reference." is this new in c++11? In c+03 this was true only for a const reference.Coadjutress
@Coadjutress In C++03 it has been true for any kind of reference. It is just that you couldn't assign a temporary to a non-const reference anyway.Chauvin
D
15

It's only ever problematic in the case where the initializer is a function call that returns a short-lived rvalue reference. With less words and more code:

// Fine; lifetime extension applies!
auto&& ref = 42;

auto id = [](int&& i) -> int&& { return std::move(i); };
auto&& uhoh = id(42);
// uhoh is now a stale reference; can't touch it!

In contrast, auto uhoh = id(42); would have worked fine.

In your case, because std::make_tuple returns a value and not an rvalue reference there is no problem.

I'm of the opinion that the real danger is from those functions and function templates with rvalue reference parameters and that return an rvalue reference to either those of some subobjects which lifetimes depend on those. (That being said, something as simple as auto&& ref = std::move(42); exhibits the problem!)

The situation is not entirely new from C++11, consider: T const& ref = bar(T_factory());.

Dimpledimwit answered 13/2, 2013 at 8:39 Comment(1)
+1, I remember stumbling on the T const& pass(T const& t) { return t; } example when trying to improve Clang's detection of returning references to temporaries... it's unfortunate that it requires inter-procedural analysis to detect possible issues :xThermocouple
L
8
  1. Yes. Any return from a function that does not return a reference type may involve a copy/move. Eliding that is what RVO is about. The object that your reference is bound to needs to be initialized somehow.

  2. No. why should it? The lifetime of a temporary/prvalue bound to a reference is determined by the scope of the reference.

  3. If func() does not return a reference type, there should not be any difference in efficiency (nor behaviour)

between

auto&& var = func();

and

auto var = func();

In both cases an object with lifetime to the end of the containing block is constructed. In one case it has its own name, in the other it is named via a reference. In both cases the name can be used as an lvalue. RVO can be equally well applied in either case.

Some compilers might optimize better for a local object than for a reference, even though in the current case the reference-to-temporary is really no different from a local object.

If func() might return a reference, things are much different - in that case you must decide whether you want to copy/move or not.

Laural answered 13/2, 2013 at 8:43 Comment(4)
If func happened to return an lvalue like Foo& func(), auto&& version will not copy while auto will copySublapsarianism
@balki: But we were discussing a value-returning function here. I now made the distinction explicit.Laural
"The lifetime of a temporary/prvalue bound to a reference is determined by the scope of the reference." is this new in c++11? In c+03 this was true only for a const reference.Coadjutress
@Coadjutress In C++03 it has been true for any kind of reference. It is just that you couldn't assign a temporary to a non-const reference anyway.Chauvin
C
1

There is a specific rule in C++ (even before C++11) that says that if you bind a reference to a temporary the lifetime of the temporary will be extended to the lifetime of the reference.

A simpler case is:

int foo () {
    return 42;
}

int bar () {
    const int& x = foo();
    return x; // Here is safe to use x
}
Clamor answered 13/2, 2013 at 8:42 Comment(4)
The rule is for const reference only. I do not think it is obvious auto && is a const reference - is it?Coadjutress
@Suma: A reference is a reference for the compiler back-end. Const-correctness only affects front-end (what is legal and what is not) and plays no role in what is undefined behavior or about performance.Clamor
It seems I am wrong. I cannot see anything about const in the standard, the corresponding sentence says "when a reference is bound to a temporary" in 12.2, at least in C++11 draft. Tutorials talk about const &, but that is probably because a plain & cannot be bound to a temporary anyway.Coadjutress
@Suma: Binding a non-const reference to a temporary is a compile error (this is sometimes annoying... for example you cannot pass a temporary to a function that needs to call non-const methods and you're forced to use a named variable instead, even if you don't need to do anything with it after the call; the rationale is that helps finding a lot of bugs introduced by implicit conversions).Clamor
S
0

The result of evaluating the call to make_tuple is a prvalue temporary of a tuple instantiation. The auto type-specifier will be inferred as the same tuple instantiation (7.1.6.4p6), so mytup is of type tuple<...> &&. The prvalue temporary is then lifetime-extended by the reference mytup (12.2p5).

  1. Because the temporary is the function's return value, there is no copy/move involved (There may still be RVO within make_tuple).
  2. The behaviour is fully defined.
  3. For almost all cases, mytup will be treated as an lvalue even though its type is an rvalue reference. However there is no point using auto && as all sensible compilers will elide the copy/move from the temporary.

To clarify, about the only way to have mytup treated as an rvalue is to use std::forward<decltype(mytup)>(mytup), as in What does auto&& tell us?; however if you know the type of mytup (tuple<...> in this case) then you may as well use std::move.

Sharasharai answered 13/2, 2013 at 8:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.