Return rvalue reference or temporary object in C++11 [duplicate]
Asked Answered
E

2

9

In Effective Modern C++ item 12, there is a sample code about the C++11 functions' reference qualifiers:

class Widget {
public:
    using DataType = std::vector<double>;
    …
    DataType& data() &            // for lvalue Widgets
    { return values; }            // return lvalue

    DataType data() &&            // for rvalue Widgets
    { return std::move(values); } // return rvalue
    …
private:
    DataType values;
};

So why does the second data() rvalue reference overload function return a temporary object DataType but not an rvalue reference DataType&&?

Edging answered 26/1, 2019 at 13:48 Comment(0)
I
6

The only reason I see would be to avoid creation of dangling reference when the object is the result of a prvalue:

Widget foo();

auto&& x = foo().data();

If foo().data() returned an rvalue reference to values member, x would be a dangling reference, because the result object of foo() is destroyed at the end of the initialization of x (the end of the full-expression).

On the other hand, with data()&& returning by value, x is bound to a temporary materialization that will have the same life time as x. So the dangling reference is avoided.

This return type for data() && is not idiomatic in C++. Usually, accessor functions return a reference and such use-case as the one above will probably raises the "dangling reference alarm" of any code reviewer.

This definition of data()&& is smart but it breaks a common convention.

Impressment answered 26/1, 2019 at 17:20 Comment(0)
W
2

The & or && after the method serve a special purpose: they serve as a ref value qualifier.

In a sense, all of the things you can put after a function's argument declarations (i.e. after the ) of the parameters) means "only use this method when *this has these qualities".

Most typically usage here is const qualifying: only calling an override in a const context:

class Widget {
void foo() {
cout << "Calling mutable foo\n";
}

void foo() const {
cout << "Calling const foo\n";
}
};

The same thing can be done with && or &, to tell the compiler to select those overrides when *this has matching qualifiers.

But why would you want to do this?

This is useful when you want to differentiate between one of the following:

  • Give out a reference to some internal data in the normal lvalue case
  • Copy out the data in the case where *this is an rvalue reference and will not outlive the calling code's reference.

A common example that bites a lot of people is the use in for loops.

Consider the following code (building on your Widget example):

Widget buildWidget() { return Widget(); }

int main() {
   for (auto i : buildWidget().data()) {
      cout << i << '\n';
   }
   return 0;
}

In this case, the && override would be called because the Widget object return by buildWidget does not have a name, and is this an rvalue reference.

In concrete terms, as soon as the call to data() is done, the underlying Widget object (unnamed here) is going to immediately die (it's lifetime will end). If this && override didn't exist, the lvalue reference into its data member would point to a destructed object. This would be undefined behavior.

In general, this rvalue ref qualifying thing is not a common thing. It really only needs to be used when you run into a situation like this (often when you want to return a value by copy when *this may be destructed after the call).

Wolcott answered 27/2, 2021 at 6:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.