JSF
JSF

Reputation: 5321

Create a temporary to pass to rvalue reference

I want to code several recursively interacting merge functions, which I think should have signatures: T&& merge_XYZ(T&& a, T&& b);

They will tend to be used recursively with lines such as:

return merge_XYZ( std::move(x), std::move(y) );

Each of these several merge functions will steal the contents of one of the inputs and inject those contents into the other input and return the result. Typically, they will have x and y which are names for what were rvalue references and thus should be converted back to rvalue references by std::move (correct me if I'm wrong).

But rarely, they have x and or y that are references to objects whose contents must not be stolen. I definitely don't want to write alternate non stealing versions of these functions. Rather, I want the caller to deal with that in these rare cases. So my main question is whether the correct way to do that is to explicitly invoke copy construction, such as:

T temp = merge_QRS( T(x), T(y) ); // use x and y without stealing yet
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ), std::move(temp) );

Main question: Is T(x) the right way to force a temporary copy to be created at that point?

Other questions:
Is T temp = the correct way to make sure the call to merge_QRS in the above code occurs before the call to merge_MNO but otherwise inexpensively forward the temporary from that into the first operand of merge_XYZ? If I used T&& temp instead does it end up holding a pointer to modified T(x) after the life of T(x)?

Is T&& the right return type (as opposed to T) for chaining a lot of these together?

How does the above compare to:

T tx = x;
T&& temp = merge_QRS( std::move(tx), T(y) ); // use x and y without stealing yet
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ), std::move(temp) );

Assuming merge_QRS will be modifying tx and returning an rvalue reference to that, is that behavior all defined?

Writing this question may have helped me realize I could be mixing together two situations that ought not to be mixed: Object you don't want to steal from vs. objects you don't want to steal from yet. Is my original merge_QRS( T(y), T(x)) right (only if consumed within the same expression) for objects I don't want to steal from? But in the case I tried as an example should I have the following:

T tx = x;  // Make copies which can be stolen from
T ty = y;
return merge_XYZ( merge_MNO( std::move(x), std::move(y) ),
                  merge_QRS( std::move(tx), std::move(ty) ) );

I think I may still be confused about stealing the contents vs. stealing the identity. If I return by T&& I'm stealing the identity of one input in addition to stealing the contents of the other. When do I get away with stealing an identity? If I return by T I'm never stealing an identity, and sometimes failing to steal an identity is inefficient.

Upvotes: 5

Views: 963

Answers (2)

Pixelchemist
Pixelchemist

Reputation: 24936

tl;dr: With value semantics you get:

  • moves happening where possible
  • No need to explictly copy arguments
  • No dangling references in case of passing prvalues to the function

Consider

struct X 
{ 
  X() = default;
  X(int y) : a(y) {}
  X(X &&r) : a(r.a) { std::cout << "move X..."; }
  X(X const &r) : a(r.a) { std::cout << "copy X...";  }
  int a; 
}; 

with foo:

X&& foo(X &&a) { return std::move(a); }

and and bar:

X bar(X a) { return a; }

Then when executing the following Code:

std::cout << "foo:\n";
X x{ 55 };
std::cout << "a: ";
X && a = foo(std::move(x)); // fine
std::cout << "\nb: ";
X && b = foo(X(x)); // !! dangling RV is not prvalue but xvalue
std::cout << "\nc: ";
X c = foo(std::move(x)); // fine, copy
std::cout << "\nd: ";
X d = foo(X(x)); // fine
std::cout << "\ne: ";
X && e = foo(X{ 12 }); // !! dangling...
std::cout << "\nf: ";
X f = foo(X{ 12 }); // fine

std::cout << "\n\nbar:\n";
X y{ 55 };
std::cout << "a: ";
X && q = bar(std::move(y)); // fine
std::cout << "\nb: ";
X && r = bar(y); // no explict copy required, supported by syntax
std::cout << "\nc: ";
X s = bar(std::move(y)); // fine
std::cout << "\nd: ";
X t = bar(y); // fine, no explict copy required either
std::cout << "\ne: ";
X && u = bar(X{ 12 }); // fine
std::cout << "\nf: ";
X v = bar(X{ 12 }); // fine
std::cout << "\n";

we obtain

foo:
a:
b: copy X...
c: move X...
d: copy X...move X...
e:
f: move X...

bar :
a: move X...move X...
b: copy X...move X...
c: move X...move X...
d: copy X...move X...
e: move X...
f: move X...

on VS 2015 and g++ 5.2.

So the only copies made (with bar) are in cases b and d, which is the desired behaviour anyway but you get rid of the possibly dangling references at the cost of 1-2 moves per operation (which afaik even may be optimized out in some cases as well).

Upvotes: 0

Richard Hodges
Richard Hodges

Reputation: 69864

Main question: Is T(x) the right way to force a temporary copy to be created at that point?

Yes

Is T temp = the correct way to make sure the call to merge_QRS in the above code occurs before the call to merge_MNO but otherwise inexpensively forward the temporary from that into the first operand of merge_XYZ?

Yes

If I used T&& temp instead does it end up holding a pointer to modified T(x) after the life of T(x)?

Yes. That's dangling reference which unfortunately the compiler won't catch.

Is T&& the right return type (as opposed to T) for chaining a lot of these together?

To be honest, it doesn't smell good to me.

You may want to reconsider your data model to be something more standard, i.e.:

T merge(T x, T y)
{
  // do some merging
  return x;
}

Copy-elision and RVO will eliminate any redundant copies. Now you can move items in, pass copies or pass temporaries. There's only one piece of logic to maintain and your code has value-semantics... which is always better (TM).

Upvotes: 4

Related Questions