5

Edit: I think the most likely use case for what I'm asking about, is when creating a function that receives a tuple of rvalue-references from std::forward_as_tuple().

The reason this question came to mind is because I was checking the members of objects passed to constructor initializers to see if they were rvalue-references (I'm open to advice telling me that this is wrong wrong wrong... hopefully followed by a rule of thumb to avoid this in the future, but that's what prompted the question). It occurred to me that, in a slightly different context, I might end up handing an object that has rvalue-reference members to multiple functions (or function objects), that I may or may not control, that may do moves from those members.

template<typename... Args>
void my_func(std::tuple<Args...>&& tup) {

    //if tup's members are rvalue references,
    //and this function moves guts from tup members, then...
    func_i_dont_control(tup); 

    //what happens here if moves are done on the same members?
    another_func_i_dont_control(std::move(tup));
}

I've looked at Use of rvalue reference members?, along with some other discussions of rvalue reference members, but I'm not quite able to definitively sort this out.

I'm not just asking what would happen, but whether this scenario should/could even happen at all, and what key rules to keep in mind when passing around objects containing rvalue-reference members.

Community
  • 1
  • 1
Brett Rossier
  • 3,420
  • 3
  • 27
  • 36

2 Answers2

1

The use of tuples and variadic templates seems to detract from the point of the question so let me rephrase it:

void func(std::unique_ptr<Foo>&& foo) {
  std::unique_ptr<Foo> bar1(std::move(foo));
  std::unique_ptr<Foo> bar2(std::move(foo));
}

What happens here ? Well, bar2 always contains a null pointer (and bar1 might, depending on the original value of foo).

There is no undefined behavior because a move constructor should leave the object in usable state citation needed, precisely for safety reasons. So you can use an object that was moved from, however its state will probably not be very interesting: it need not even be equivalent to the default constructed state, it can just be a stale state.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    This example just won't compile without `std::move(foo)` – visitor Jan 13 '12 at 09:03
  • When you mentioned leaving the object in a usable state, I immediately thought of a really long article covering r-value references and perfect forwarding. See the bottom paragraph of [this page](http://thbecker.net/articles/rvalue_references/section_04.html). – Michael Kristofik Jan 13 '12 at 16:07
  • @Kristo: yes, the double-free issue can crop up for some cases. Fortunately in most situations you have the pointer to memory/file/whatever so you are left with a null pointer for the destructor to play with. An interesting effect is that this does not work well with RAII classes: after moving the instance no longer has a resource, contrary to what the idiom suggest. – Matthieu M. Jan 13 '12 at 18:17
1

In this code func_i_dont_control cannot silently steal the parameter. Only rvalues bind to rvalue references and a named variable is not a rvalue. Your code either fails to compile, or func_i_dont_control (has an overload that) doesn't use move semantics.

To give func_i_dont_control the chance to steal the tuple (to bind the tuple to a rvalue reference), it would have to be explicitly cast to rvalue with std::move(tup).

(A function taking a lvalue reference should not move from it, otherwise we really can't tell what would happen.)


Edit But the question doesn't seem to be about the tuple itself but its members. Again, those members won't be rvalues, so func_i_dont_control would need to move them explicitly. I don't think it has a "moral right"* to do so, unless it receives the whole tuple as a rvalue, which is not happening in your function.

* With move-semantics you have to follow certain guidelines. Basically you can cast anything to rvalue and move from it, it doesn't matter if you are dealing with lvalue or rvalue references. As long as you follow these guidelines, move-semantics will work correctly. If you start casting things to rvalues disregarding those guidelines, objects will start disappearing after function calls etc.

visitor
  • 1,781
  • 10
  • 7
  • So the very act of passing an object with rvalue-reference members as a parameter (giving it a name), will change the object's members? How does `std::forward_as_tuple()` work then if I'm taking its result and handing it to a function that ends up naming the object? I'm not trying to be difficult, my head is just spinning trying to nail this down, lol. – Brett Rossier Jan 13 '12 at 15:37
  • Note however that formally this could mess things up even without `func_i_dont_control` moving things away. In principle `func_i_dont_control` could store a pointer to its argument for later usage, which would point to a moved-away object later. However I think that in itself would be bad style (especially it would be bad style to not prominently document that behaviour, so that you know not to pass objects which might go away; if it is documented, you'd not call that function on an rvalue reference argument). – celtschk Jan 13 '12 at 15:42