It is very rarely a good idea to return an rvalue reference. The rare exceptions are cases where you are writing a function logically equivalent to std::forward.
The reason why it is a bad idea is that reference lifetime extension is not transitive, and you can easily get dangling references accidentally.
const auto& foo = some_operation;
if some_operation is a prvalue expression, the lifetime of the temporary is extended to that of foo. However, if some_operation is instead:
foo( some_prvalue_expression )
where foo takes an rvalue reference and returns it, instead you silently get a dangling reference.
For types that are cheap to move, the solution is simple:
struct B{
template<class Ar> friend
Ar operator<<(Ar&& ar, A const& a){...; return std::forward<Archive>(ar);}
}
This returns by reference if passed an lvalue, but by value if passed an rvalue.
Now reference lifetime extension works correctly.
This requires that your type be cheap-to-move; so
myarchive_type{} << B{} << B{} << other_unrelated_type{};
results in the myarchive_type{} being moved 3 times.
If your type isn't cheap to move, consider simply blocking the rvalue version; the lack of safety makes it not worth it.
For a concrete example, look at this function:
template<class C>
struct backwards {
C c;
auto begin() const {
using std::rbegin;
return rbegin(c);
}
auto end() const {
using std::rend;
return rend(c);
}
};
template<class C>
backwards(C&&) -> backwards<C>;
(Apologies if I got the deduction guide wrong).
Now we do this:
for( auto x : backwards{ std::vector<int>{1,2,3} } ) {
std::cout << x << ",";
}
the vector is moved into backwards, but if we do this:
auto vec = std::vector<int>{1,2,3};
for( auto x : backwards{ vec } ) {
std::cout << x << ",";
}
no copy is made.
Without the "create a copy and return it if we are passed an rvalue", the first for loop above instead has a dangling reference in it.
I've seen a few proposals or ideas for proposals to try to generalize reference lifetime extension, or at least provide compilers with hints that the return value of a function depends on the lifetime of the arguments passed to it so that it can generate errors/warnings when you are using dangling reference this way. None, as far as I know, have made it into a C++ standard.
Until one does, casually returning an rvalue reference is simply too dangerous for my blood.
Suppose you consider moving expensive. Well this works:
std::invoke([&](auto&& ar){
dosomething( ar << << B{} << B{} << other_unrelated_type{} );
}, myarchive_type{} );
it takes the temporary myarchive_type{} and stores it in an rvalue reference.
Then it passes it to a lambda. Inside the lambda, the name ar is an lvalue. We proceed to use it safely within the lambda.