0

I am attempting to learn how to make a menu that loops if invalid input is entered.

I am trying to have the loop control determine if the loop should quit or looping or not.

If I do an invalid selection like "k", it continually loops without prompting me for input.

    int num = 0;
    bool isValid = true;
    do {
        num = 0;
        cout << endl << "---MENU---\n" << endl;
        cout << "1- Insert\n";
        cout << "2- remove\n";
        cout << "3- SeqSearch\n";
        cout << "4- PrintList\n";
        cout << "5- Quit\n" << endl;
        
        cout << "Selection> ";
        cin >> num;
        cin.clear();
        cin.ignore();

        switch (num) {
            case 1: 
                num = 1;
                cout << "Insert\n";
                break;
            case 2: 
                num = 2;
                cout << "remove\n";
                break;
            case 3: 
                num = 3;
                cout << "SeqSearch\n";
                break;
            case 4: 
                num = 4;
                cout << "PrintList\n";
                break;
            case 5: 
                num = 5;
                cout << "Exiting the program.\n" << endl;
                exit(0);
            default:
                cout << "Invalid Selection\n";
                isValid = false;
                break;
            }
            
    } while (!isValid);
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • `cin >> num;` with invalid entry makes `std::cin` in invalid state. [`std::cin.clear`](https://en.cppreference.com/w/cpp/io/basic_ios/clear), [`cin.ignore`](https://en.cppreference.com/w/cpp/io/basic_istream/ignore) might help. – Jarod42 Sep 07 '22 at 12:52
  • You can't have your cake and eat it too. Your code is reading an integral value from a user, which means the input stream itself will treat input like `k` as an error - not magically set the value of your variable `control`. – Peter Sep 07 '22 at 12:52
  • `num = 1;` ... `num = 5;` is pointless you know that num is already those values. With that said I expect any reasonable compiler will optimize this out in release / optimized mode. – drescherjm Sep 07 '22 at 14:38
  • @andreasWenzel I found the issue as I simply called the function twice. – Sorted Sand Sep 07 '22 at 15:11

2 Answers2

2

Before using the result of cin >> num;, you should test whether the input conversion was successful or not. One simply way to do this would be to use cin.operator bool.

std::cout << "Selection> ";
std::cin >> num;

//test for conversion failure
if ( !std::cin )
{
    std::cout << "Unable to convert input to integer!\n";

    //clear fail flag
    std::cin.clear();

    //discard remainder of line from input stream
    std::cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );

    continue;
}

//input was successful, so we can now use the result
switch ( num )
{
    [...]
}

It is important to note that I am discarding the remainder of the line after every attempt to read an integer from the input stream. If I did not do this, then if the user entered k, then the k would stay on the input stream, causing the conversion to fail again in the next loop iteration.

Note that you will have to #include <limits> in order to use std::numeric_limits.

However, this solution still is not perfect, because if the user enters for example 6k, then the conversion of the 6 will be successful in the current loop iteration, but it will leave k on the input stream, so that in the next loop iteration, the program will attempt to convert that k to an integer, instead of waiting for the user to enter more input. For this reason, it would probably be better to

  • check the remainder of the line for non-whitespace characters, and treat the entire line as invalid if such a character is found, even if std::cin >> num; is successful.

  • discard the remainder of the line in all cases, even in cases in which std::cin >> num; is successful, and

Here is the full code solution which also fixes the issues mentioned above:

#include <iostream>
#include <limits>
#include <cctype>

int main()
{
    for (;;) //infinite loop, equivalent to while(true)
    {
        int num;
        char c;

        std::cout <<
            "\n"
            "---MENU---\n"
            "\n"
            "1- Insert\n"
            "2- remove\n"
            "3- SeqSearch\n"
            "4- PrintList\n"
            "5- Quit\n"
            "\n";

        std::cout << "Selection> ";
        std::cin >> num;

        //test for conversion failure
        if ( !std::cin )
        {
            std::cout << "Unable to convert input to integer!\n";

            //clear fail flag
            std::cin.clear();

            //discard remainder of line from input stream
            std::cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );

            continue;
        }

        //verify that remainder of line is either empty or
        //contains harmless whitespace characters, and
        //discard this remainder
        while ( std::cin.get( c ) && c != '\n' )
        {
            if ( !std::isspace( static_cast<unsigned char>( c ) ) )
            {
                std::cout << "Invalid character found!\n";

                //discard remainder of line
                std::cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );

                //we cannot use "continue" here, because that would
                //go to the next iteration of the innermost loop, but
                //we cant to go the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        //input was successful, so we can now use the result
        switch ( num )
        {
            case 1: 
                num = 1;
                std::cout << "Insert\n";
                break;
            case 2: 
                num = 2;
                std::cout << "remove\n";
                break;
            case 3: 
                num = 3;
                std::cout << "SeqSearch\n";
                break;
            case 4: 
                num = 4;
                std::cout << "PrintList\n";
                break;
            case 5: 
                num = 5;
                std::cout << "Exiting the program.\n\n";
                return EXIT_SUCCESS;
            default:
                std::cout << "Invalid Selection\n";
        }

    continue_outer_loop:
        continue;
    }
}

Note that this program uses one goto statement. Normally, goto should be avoided, but for breaking out of a nested loop, it is acceptable.

This program has the following behavior:


---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> k
Unable to convert input to integer!

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 6k
Invalid character found!

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 0
Invalid Selection

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 1
Insert

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 2
remove

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 3
SeqSearch

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 4
PrintList

---MENU---

1- Insert
2- remove
3- SeqSearch
4- PrintList
5- Quit

Selection> 5
Exiting the program.
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
0

You should add

cin.clear();
cin.ignore();

after cin >> num;

nobody1879
  • 41
  • 4
  • 2
    No, not as a general rule. Yes, **if** the previous input has failed, but you have to check for that. – Pete Becker Sep 07 '22 at 13:05
  • `cin.ignore();` will only ignore a single character. Is that what you want? Or do you want to ignore the remainder of the line? – Andreas Wenzel Sep 07 '22 at 13:10
  • That has solved the second issue, but for some reason when I enter in valid input, I have to enter it in twice. – Sorted Sand Sep 07 '22 at 13:44
  • Per the comments, you don't just put these lines in there. You do an error check, and only execute them `if` there is an error. The other answer is more complete. – sweenish Sep 07 '22 at 13:50
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 13 '22 at 13:08