Passing std::forward_as_tuple() result to multiple functions that may move from that object's rvalue-reference members?

StackOverflow https://stackoverflow.com/questions/8848067

Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top