18
votes
  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?
4
A reference is a reference. - R. Martinho Fernandes
regarding auto&& var = func() , stackoverflow.com/questions/13618506/… - billz
@billz 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. - balki
@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). - Luc Touraille
@6502 Yeah, indeed. People that cannot handle references in C++03 won't magically get their lives made easier by C++11, but well. - Christian Rau

4 Answers

15
votes

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());.

8
votes
  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.

1
votes

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
}
0
votes

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.