Why this matters
Many programs wait for a person to tell them what to do next. A bank terminal shows a list of options and waits for a number. A text editor waits for a command key. A small tool asks "add, list, or quit?" and keeps asking until the user is done. Each of these is a menu: a list of choices, a prompt, and a loop that repeats until the user picks the exit option.
The idea is simple, but the details are where beginners get stuck. The program must not run forever when the user is finished. It must not crash or loop without end when the user types something unexpected. It must handle the case where the input stream closes, for example when the user presses Ctrl+D on Linux or Ctrl+Z on Windows, or when the input comes from a file. This lesson builds a menu step by step, and shows the mistakes that cause a menu to hang or quit by accident.
The lesson uses the while loop and the switch statement from earlier in this course. The digit loops in the previous lesson show how to process a number once it is read. This lesson is about the loop around the reading itself.
The idea: ask, act, repeat
Every menu follows the same three steps, and the loop repeats them.
- Ask. Show the list of choices and a prompt, such as
Choose:. - Act. Read the answer and run the matching action.
- Repeat, or stop. If the answer was the quit choice, leave the loop. Otherwise go back to step 1.
Think about a vending machine. It shows the options, you press one, it gives you the item, and it shows the options again. It stops only when you walk away. A menu program does the same thing, but its "walk away" is the quit choice.
Two terms need a plain definition before the examples. The input stream is the channel the program reads from. By default it is the keyboard, but it can also be a file or the output of another program. The name cin is the C++ object for the input stream from the keyboard. A sentinel is a special value that tells a loop to stop. In a menu, the quit choice is a sentinel.
Example 1: a menu with a number choice
Start with the smallest possible menu. It offers three choices: say hello, say goodbye, or quit. The user types a number, and the program does what the number asks.
The variable choice starts at -1. That value is not on the menu, so it cannot match the quit choice, which lets the loop start. The loop test choice != 0 keeps the loop running until the user enters 0.
The line if (!(cin >> choice)) does two jobs at once. The expression cin >> choice reads one whole number from the input. If the read succeeds, the expression is true, and the ! makes the if test false, so the program continues. If the read fails, for example because the input has ended or the user typed letters, the expression is false, and the if block runs. The block prints a short message and break leaves the loop. The break keyword stops the nearest loop immediately.
To run the program with prepared input, pipe the input into it. The printf command below writes four lines, and the program reads them as if the user had typed them.
$ printf '1\n2\n7\n0\n' | ./menu1_fixed
1) Say hello
2) Say goodbye
0) Quit
Choose: Hello.
1) Say hello
2) Say goodbye
0) Quit
Choose: Goodbye.
1) Say hello
2) Say goodbye
0) Quit
Choose: Unknown choice: 7
1) Say hello
2) Say goodbye
0) Quit
Choose: Bye.The menu prints its choices before each read, so each answer is followed by a fresh list. The output shows the three lines of the switch in action: a known choice runs its action, and the unknown choice 7 falls to the default branch. The 0 quits the loop, and the program returns 0. When the input is piped, the prompt and the output appear on the same line, because no one presses Enter after the prompt. At a real keyboard, each answer appears on its own line.
Mistake: the loop that never ends
The version above has a check that the first version of this menu did not have. Here is the first version. It is the same program with the if check removed.
Give this program the input 1 followed by the end of the input, and it never stops. Running it with printf '1\n' | timeout 2 ./menu1 | head -c 200 shows the same menu and answer again and again:
1) Say hello
2) Say goodbye
0) Quit
Choose: Hello.
1) Say hello
2) Say goodbye
0) Quit
Choose: Hello.
1) Say hello
2) Say goodbye
0) Quit
Choose: HelThe output was cut at 200 bytes by the command, and the timeout command stopped the program after 2 seconds. Without the time limit, the program would print forever.
The reason is subtle. The first read takes the 1 and sets choice to 1. The second read finds that the input has ended. A read at the end of the input does not change the variable it was asked to fill, so choice stays 1. The switch runs the case for 1 again, and the loop tests choice != 0, which is still true. The cycle never ends.
The rule to remember is this: always test whether the read worked before you use the value. The if (!(cin >> choice)) line in example 1 is that test. It is also a common pattern for any loop that reads input.
Mistake: bad input that quits the program
Now try the first version with letters instead of a number. Type abc and press Enter.
$ printf 'abc\n' | ./menu1
1) Say hello
2) Say goodbye
0) Quit
Choose: Bye.The program quits with the message for the quit choice, even though the user never picked it. The reason is that a failed read of a number does not leave the old value alone the way the end of input does. When the input is not a number, the read fails and stores 0 in choice, which matches the quit choice. The program then exits without any warning.
In example 1, the if (!(cin >> choice)) check catches this case as well, because the read fails when the input is letters. But the message it prints is for the end of the input, not for bad input, and the loop still ends. A better menu keeps going after a bad answer. The next example shows how.
Example 2: read whole lines
Reading a number with cin >> choice has a weakness. The user may type a choice followed by spaces, or type a word by mistake, and the read logic has to handle each case. A common and more forgiving approach is to read a whole line of text with getline, then check the text.
The getline function reads everything up to the end of the line and stores it in a string. A string is a C++ type for text. The function returns false when there is nothing more to read, which gives the loop a clear end. The next program does nothing except echo each line back, so you can see how getline behaves.
Give it the input hi there, then an empty line, then last with no newline after it. The output is:
You typed: [hi there]
You typed: []
You typed: [last]
End of input.Three points come out of this output. The line with a space is read as one piece, so hi there is not split. The empty line is read as an empty string, and it does not end the loop. The last line has no newline after it, and getline still returns it, so a final line without a newline is not lost. The loop ends only when the input runs out, and the program then prints the closing message.
Example 3: a task list menu
Now put the pieces together. This program keeps a list of tasks in a vector, which is a resizable list from the standard library. The user can add a task, list the tasks, remove a task by its number, or quit. The menu reads each answer as a whole line, converts it to a number, and handles any answer that is not on the menu without stopping.
The loop is while (true), which never ends on its own. Every way out is a break: the input closed, the quit choice was chosen, or a read failed while the program waited for a task's text. A loop with no test in its header is correct here only because each break is written on purpose. When you write one, check that every path out of the loop is one you intend.
The conversion stoi(line) turns the text into an int. If the text is not a number at all, the function throws an invalid_argument exception, which is an error signal the program can catch. The try and catch blocks catch that signal, print a message, and use continue to skip the rest of this pass and show the menu again. The continue keyword jumps to the next pass of the loop. This is how a bad answer stops being a crash and becomes a second chance.
Here is a session with a few mistakes in it. The input has an empty answer, a task that is added twice, a removal, a bad removal number, and an unknown choice. The printf command below writes these lines to the program.
$ printf '2\n1\nBuy milk\n1\nWrite lesson\n2\n3\n1\n3\nx\n9\n0\n' | ./menu_todo
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> No tasks yet.
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> Task text: Added task 1.
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> Task text: Added task 2.
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> 1. Buy milk
2. Write lesson
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> Task number: Removed.
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> Task number: Please type a task number.
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> Unknown choice: 9
1) Add a task
2) List tasks
3) Remove a task
0) Quit
> Goodbye.The first answer is 2, so the list is empty and the program says No tasks yet. The answer 1 asks for the task text on the next line, which is Buy milk. The second add gives Write lesson. Listing shows both tasks with their numbers. The answer 3 asks for a task number, and the answer 1 removes Buy milk. The next 3 asks again, and the answer x is not a number. The stoul function throws an exception on that input, and the catch block prints Please type a task number. The answer 9 is not on the menu, so it falls through the else branch. The answer 0 ends the loop, and the program says goodbye.
Notice that the removal uses tasks.begin() + static_cast<ptrdiff_t>(index - 1). The user counts tasks from 1, and the vector counts from 0. The subtraction turns the user's number into a position in the list. ptrdiff_t is the type for the distance between two list positions, and static_cast converts index - 1 to it. The check index < 1 || index > tasks.size() runs first, so the subtraction never points outside the list.
A quirk of stoi
The function stoi reads as much of the text as it can and ignores the rest. This means stoi on the text 2abc returns 2 without an error. A small test program shows the difference.
The output is:
2
invalid_argument: stoiA menu that accepts 2abc as the choice 2 may surprise a user, but it does not crash. If the menu must reject partly numeric input, check the text with a second function, or read the number with istringstream and test that the whole text was used. For a learning menu, the simple version above is enough.
Common mistakes
Mixing cin >> with getline
The cin >> operator reads a value and leaves the Enter key's newline in the input. A later getline reads that leftover newline as an empty line. This program shows the problem.
With the input 5 and then hello on the next line, the output is:
n=5 name=[]
name=[hello]The first getline read the empty rest of the first line, so name is empty. The fix is to read every line with getline and convert the text with stoi, as example 3 does. If you must use cin >>, call cin.ignore() once before the getline, to throw away the leftover newline. With that one line added and the input 5 then hello, the first line of output becomes n=5 name=[hello].
Forgetting the sentinel
A menu needs one choice that ends it. If the quit choice is missing, or it is handled in a branch that never runs, the program has no way out except the end of the input or the user stopping it. Check that the quit branch is reachable with every kind of input, including an empty line.
Practice
Exercise 1. Change example 1 so that the menu has a fourth choice, 3) Count choices, which counts how many valid choices the user has made so far, including the quit choice when it is entered. Print the count when the user quits. Hint: a variable that goes up by one each time through the loop is a counter.
Show the answer
Run with the input 1, 3, 9 and 0, the output ends with Choices so far: 2, then Unknown choice: 9, then Bye after 4 choices. The counter goes up after every successful read, so it counts the unknown choice too. If you want to count only valid choices, move the ++count into the cases that are valid.
Exercise 2. Write a menu that reads whole lines and answers the words hello, bye and quit. Any other text should print I do not know that word. The program must exit cleanly on quit and when the input ends.
Show the answer
With the input hello, wave, bye and quit, the output is Hello to you., I do not know that word., See you. and Goodbye.. The program then prints Program finished. after the loop. The getline test handles the end of the input, so the program also ends cleanly when there is no quit line.
Exercise 3. Write a program that starts with the tasks Buy milk and Write lesson in a vector. It reads lines in a loop. The answer 4 asks Are you sure? (y/n), and only the answer y clears the list. The answer 0 quits. Print the number of tasks left when the program ends.
Show the answer
With the input 4, n, 0, the output is Are you sure? (y/n) Nothing was cleared. Tasks: 2 and then Tasks left: 2. With the input 4, y, 0, the output is Are you sure? (y/n) Cleared. and then Tasks left: 0. The answer line is read with getline too, so the second line of input is not mixed up with the menu choice. A vector has a clear member function that removes every item.
Exercise 4. Explain why the failed-read branch in example 1 uses break and not continue.
Show the answer
continue would go back to the top of the loop and print the prompt again. After a failed read, the input is closed, so the next read fails too. The loop would print the prompt forever. break leaves the loop at once, so the program prints the closing message once and returns. This is the same reason the program in the mistakes section hangs: a loop that keeps trying to read a closed input must have a way out.
Exercise 5. Write a menu that asks for a number between 1 and 5 and repeats the question until the user enters a number in that range. Input that is not a number must also be rejected, and the program must not loop forever if the input ends.
Show the answer
With the input 9, x and 3, the output is That is not in the range., then the program stops on x and prints the closing message, with a return code of 1. With the input 9 and then 3 on separate lines, the program prints That is not in the range. and then You chose 3.. The cin failure on x is caught before the range check, so the letter never reaches the comparison. The return code 1 tells the calling program or the shell that the input was not valid.
Summary
- A menu is a loop with three steps: ask, act on the answer, and repeat or stop.
- Test every read before you use its value, because a failed read can leave the old value in place or write a zero.
- Read answers as whole lines with
getline, and convert the text withstoiinside atryblock, so a bad answer causes a message, not a crash. - Every path out of a loop with no test in its header must be a
breakyou wrote on purpose. - Give the quit choice its own branch, and check that the program also exits when the input ends.
- Do not mix
cin >>withgetlinewithout clearing the leftover newline first.