Skip to content

← Copy, Move, and the Rule of Zero step 2 of 5

Medium Primitives

When std::move selects a copy

std::move(x) does not transfer resources by itself. It casts the expression to an rvalue reference, allowing a receiving operation to select a move overload. A constructor or assignment operator performs the actual transfer.

For the Payload type provided below:

Payload source;
Payload destination = std::move(source);  // calls Payload(Payload&&)

Payload contains a vector. Its move constructor moves that member instead of copying the vector's elements. Other types define their own move behaviour; a move does not universally mean "empty the source".

Why a call to std::move can still copy

The starter uses a const reference to each payload:

const Payload& part = parts[0];
out.push_back(std::move(part));

std::move preserves const, so this expression has type const Payload&&. The provided move constructor takes Payload&& and cannot accept it. The copy constructor takes const Payload&, so it can be used instead.

With a mutable reference, the move overload becomes available:

Payload& part = parts[0];
out.push_back(std::move(part));

This rule concerns the overloads available for this type. A type can declare other constructors, including one taking const T&&; the usual resource- transferring move takes T&& because it needs to modify the source.

Two other cases are worth recognizing:

  • A function taking only const Payload& still receives a reference when called with std::move(payload). Passing that reference does not copy or move the payload; what the function does inside determines any transfer.
  • If no move constructor is declared, a compatible copy constructor may be used. This differs from explicitly declaring T(T&&) = delete: if that deleted overload is selected, construction fails to compile.

Returning the result

Use return out; for this function's local vector. The compiler can construct it directly as the result, avoiding a move through named return value optimization (NRVO). If NRVO is not applied here, the vector can be moved. Writing return std::move(out); prevents NRVO for that return expression.

Your task

std::vector<Payload> take(std::vector<Payload>& parts, int limit);

Move the first limit payloads into a new vector, preserving their order. Take all payloads if limit exceeds the size, and none if limit <= 0. Keep the size of parts unchanged and leave unselected elements untouched. Input sizes fit in int for this exercise.

For payload data [[1, 2], [3], [4]] and limit = 2, the returned vector contains [[1, 2], [3]], and parts still has three elements. Do not rely on the previous contents of an element after moving from it.

Leave Payload unchanged. Its copy counter must remain zero during take. The harness reports the returned data, the copy count and the remaining size of parts. Clang-tidy's performance-move-const-arg check can also identify the const reference that causes the starter to copy.