Skip to content

← Errors step 1 of 4

Easy Primitives

Zero is an answer

A function that might not have an answer has to say so somehow. The traditional way is to pick a value from the return type and declare that it means “nothing”:

int average(...);        // -1 if there is nothing to average
std::string name(...);   // "" if unknown
Node* find(...);         // nullptr if absent

Two of those are lies, and the third is fine.

-1 is a perfectly good average. "" is a perfectly good name. The sentinel works right up until the data contains it, at which point a real value is reported as an absence and no test you wrote will notice. nullptr is the honest one, because there is no such thing as a real pointer that is null — the type has a spare value and always did.

std::optional<T>

std::optional<int> average(...);

An optional<T> is a T plus a flag, stored inline — no allocation, no indirection. It either holds a value or it does not, and the compiler makes you say which case you are in before you can read it.

std::nullopt the empty one
o.has_value(), or just if (o) is there a value
*o, o->field the value — only if there is one
o.value() the value, or throws std::bad_optional_access
o.value_or(fallback) the value, or that

*o on an empty optional is undefined behaviour, exactly like dereferencing a null pointer. value() is the checked version; value_or is the one you want when a default is genuinely correct.

The trap this problem is about

std::optional<int> f() {
    return 0;        // NOT empty. An optional holding zero.
}

std::optional<int> converts from int, so return 0; produces an engaged optional whose value is 0. If you meant “no answer”, the word is std::nullopt, and the difference is invisible at the return statement and very visible to the caller.

What optional is not for

  • Not for errors. Optional says “no value”, not “why”. If the caller needs the reason, return something that carries it — an error enum, or std::expected<T, E> in C++23.
  • Not optional<T&>. It does not exist in C++20. A maybe-present reference is a T*, which is what Track 4 said.
  • Not for a value you always have. optional<T> in a struct where the field is always set is a check every caller has to write for nothing.

Your task

std::optional<int> average(const std::vector<int>& values, int lo, int hi);

The integer average — truncated toward zero, as C++ division does — of the values in the closed range [lo, hi]. If no value is in range, there is no average.

summarize is given and calls it per group; do not change it. The starter returns a number for the empty case, which is one of the two things it could have meant.