Skip to content

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

Easy Primitives

The copy you did not write

In most languages, passing an object around passes a reference to it. In C++ it copies the object — the whole thing, every element, every allocation — and the syntax for that is nothing at all.

for (Payload p : items) { ... }        // copies every element
std::vector<int> d = p.data;           // copies the vector
void handle(std::string name);         // copies the caller's string

None of those lines contain a word that suggests expense. That is the whole problem: C++ makes the expensive thing invisible and the cheap thing — const& — six characters longer.

What a copy actually does

Copying a std::vector<int> of a million elements allocates four megabytes and memcpys into it. Copying a struct that contains three such vectors does it three times. In a loop over ten thousand items, that is a program that looks like it is doing arithmetic and is in fact doing allocation.

This is not a micro-optimisation. It is the single most common reason C++ code is slower than the C it replaced, and it is nearly always accidental.

The two lines that cause most of it

1. The range-for that copies.

for (Payload p : items)          // copy per element
for (const Payload& p : items)   // no copies at all
for (Payload& p : items)         // no copies, and you may modify

The & is the entire difference. Write const auto& by default; drop the const when you need to modify; drop the & only for genuinely cheap types like int, where a reference costs more than the copy it saves.

2. The named copy of something you only read.

std::vector<int> d = p.data;     // copies
const std::vector<int>& d = p.data;   // names it, copies nothing

A reference is a name for an existing object. If all you do is read through the name, that is all you needed.

clang-tidy knows

Both of these are checks in the performance-* group that the gate enables: performance-for-range-copy and performance-unnecessary-copy-initialization. You will meet them here on purpose, so that when they fire on your own code later you read them as arithmetic rather than as nagging.

Your task

std::vector<int> heads(const std::vector<Payload>& items);

Return the first element of each non-empty payload, in order. Empty payloads contribute nothing.

The starter is correct and does it with a copy per item. Payload counts its own copies, and the harness reports that count alongside your answer — so a correct-but-copying solution fails, which is the judgement a reviewer would make and a normal test suite would not.

Get the count to zero.