Skip to content

← Values, Types, Initialisation step 2 of 4

Medium Primitives

Handle signed counts and unsigned sizes

A container length and a requested count can use different integer types. Before subtracting or comparing them, check which type the expression uses.

A negative integer can become a large unsigned value

For std::vector<int> in this environment, size() returns an unsigned size type corresponding to std::size_t. With the usual 64-bit size_t and 32-bit int, the comparison below converts i to the unsigned type:

int i = -1;
bool inside = i < xs.size();  // false: converted -1 is the largest size_t

This does not test whether i is a valid index. In this example, -1 becomes 2^64 - 1, so it is not less than any size_t value. More generally, C++ first promotes integer operands, then selects a common type using their ranks and representable ranges. The result is not always unsigned just because one original operand was unsigned.

A loop with int i = 0; i < xs.size(); ++i can work for small containers. The mixed types make it harder to reason about, and int cannot count through a container larger than its range without signed overflow.

Subtraction has a different trap

On this platform, subtracting 1 from an unsigned size of zero wraps to the largest size_t value:

for (std::size_t i = 0; i <= xs.size() - 1; ++i) {
    // xs[i] would already be out of bounds at i == 0 if xs is empty.
}

Use i < xs.size() for forward iteration. When no index is needed, a range-for avoids this size calculation entirely. For reverse iteration, use reverse iterators or a loop whose condition handles zero before decrementing. An unsigned index alone does not make subtraction safe.

C++20's std::ssize(xs), available from <iterator>, provides a signed length. It can simplify arithmetic that needs negative results, provided the length and intermediate calculations fit the signed type. It does not check for overflow or validate an index.

Your task

std::vector<int> last_n(const std::vector<int>& xs, int n);

Return a new vector containing the last n elements in their original order. Leave xs unchanged:

Input xs n Result
[1, 2, 3, 4, 5] 2 [4, 5]
[1, 2] 5 [1, 2]
[1, 2] 0 or a negative value []
[] any supported n []

When n is at least the length, return all elements. Handle n <= 0 before subtracting. Test values and n fit int, and vector lengths fit std::int64_t.

In the starter, xs.size() - n converts n to the unsigned size type before subtracting. With two elements and n = 5, start wraps to a large value. The following i < xs.size() condition is false immediately, so the function incorrectly returns an empty vector. It does not read past the end in that case.

Check the boundary cases first, then compute a start index whose range you have established. Casting an unchecked result only changes its type; it does not fix the reasoning that produced it.