#053 – Input validation in C++. Why std::cin >> x is fragile – Part 1

Most programs prompt the user for input at some point — a menu choice, a configuration value, a calculator’s operands.

The natural tool for the job is std::cin >> x, and it works perfectly when the user cooperates. However, we can’t expect the user to always cooperate.

Enter a number to populate the length of the array: %
// How does the program respond?

Type a letter where a number is expected, paste a string with extra characters and std::cin >> x either fails silently or produces nonsense. Since std::cin is fragile, we must arm our programs with robust input validation.

The program must detect invalid errors and handle them gracefully.

This Nibble discusses how std::cin >> x breaks; Part 2 covers what to do about it.

Validating Input

The procedure of validating input is as follows:

  1. Let the user enter whatever they want
  2. Let std::cin and >> attempt to extract it
  3. Detect and handle the error cases gracefully.

A program in dire need of input validation

Consider a simple calculator program. Let’s intentionally enter invalid input to test its robustness.

#include <iostream>

double getDouble()
{
    std::cout << "Enter a decimal number: ";
    double x{};
    std::cin >> x;
    return x;
}

char getOperator()
{
    std::cout << "Enter one of +, -, *, /: ";
    char op{};
    std::cin >> op;
    return op;
}

void printResult(double x, char op, double y)
{
    std::cout << x << ' ' << op << ' ' << y << " is ";
    switch (op)
    {
        case '+': std::cout << x + y << '\n'; return;
        case '-': std::cout << x - y << '\n'; return;
        case '*': std::cout << x * y << '\n'; return;
        case '/': std::cout << x / y << '\n'; return;
    }
}

int main()
{
    double x{ getDouble() };
    char   op{ getOperator() };
    double y{ getDouble() };
    printResult(x, op, y);
}

Failure mode 1: extraction failure

When the user types something >> cannot parse into the target type, the extraction fails. Three things happen:

  1. The stream enters a fail statestd::cin.fail() returns true, and all subsequent reads on the stream are skipped until the state is cleared.
  2. The offending characters remain in the input buffer.
  3. Since C++11, the target variable is set to zero on extraction failure for arithmetic types.
// Let's enter a as a number
Enter a decimal number: a
Enter one of +, -, *, /: Enter a decimal number: 0 0 0 is

What happened: the a failed to parse as a double, so cin entered the fail state and x became 0. The second prompt printed, but the read was skipped because the stream was still in a fail state. The third prompt printed, the read was skipped again, and y was left at its initialiser (0). op was left at its initialiser ('\0'), the switch matched no case, and the program printed nothing after is.

This is the most common and most catastrophic failure mode. A single bad character cascades through every subsequent input.

Failure mode 2: leftover characters in the buffer

A successful extraction can still leave the buffer in a state that breaks the next read. std::cin >> op reads exactly one character into a char, which means the rest of the line stays in the buffer to be read later. If the user types 2a at a prompt for a double>> extracts the 2 as a valid number and leaves the a behind:

Enter a decimal number: 123
Enter one of +, -, *, /: 4

// Let's enter 2a
Enter a decimal number: 2a
123 4 2 is

The first read got 123. The operator read got 4 — note that this is also the wrong type for getOperator, but a char happily accepts the digit 4. The second number read got 2 and left the a in the buffer. The switch then fell through because '4' is not one of + - * /, so no result was printed. Two distinct kinds of broken input compound here, and the program produces no error message.

Failure mode 3: semantically invalid input

Even when extraction succeeds and the buffer is clean, the value itself can still be wrong for the program’s purposes. The switch in printResult covers four operators and has no default case. Type t as the operator:

Enter a decimal number: 12.2
Enter one of +, -, *, /: t
Enter a decimal number: 11
12.2 t 11 is

Extraction succeeded — t is a perfectly valid char. The buffer is clean. The value is just not one the program knows how to use. The switch silently does nothing, and the user is left staring at a half-finished sentence.

Ultimately

As you can clearly see, this program is not bulletproof. It’s in urgent need of input validation. #054 – Input validation in C++, Part 2: making std::cin bulletproof – Part 2 will discuss strategies we can use to strength the input validation. See you there!