0

The below code serves as a simple terminal window menu which I will later build into a larger application. Letters correspond to an operation the user would like to select and to do so the user enters the singular corresponding letter. Illegitimate responses are met with an error. Typing nothing and pressing Enter prints an arrow on the next line and awaits valid user input. The menu currently serves its desired purpose, though, it's unpolished in its current presentation.

#include <iostream>

int main()
{
    std::cout << "Operations:\n" << "\ta)\n" << "\tb)\n" << "\tc)\n\n";

    char choice;
    bool check;
    
    do{
        std::cout << "> ";
        std::cin >> std::noskipws >> choice;
        switch (choice) {
            case 'a':
            case 'A':
            {
                std::cout << "A worked!\n";
                check = false;
                break;
            }
            case 'b':
            case 'B':
            {
                std::cout << "B worked!\n";
                check = false;
                break;
            }
            case 'c':
            case 'C':
            {
                std::cout << "C worked!\n";
                check = false;
                break;
            }
            case 32:
            case 9:
            case 10:
            case 13:
            {
                check = true;
                break;
            }
            default:
            {
                std::cout << "\nERROR. Select an operation from the above menu by typing its corresponding letter\n";
                check = true;
            }
        }
    }while(check == true);
return 0;
}

I intended for the newline/return to function similar to a regular terminal window where the prompt (in my case, just "> ") follows your cursor and if no characters which constitute whitespace are typed in, no error is returned. Currently, only newline/return alone yields just that, but every additional character input now increases the output messages correspondingly.

For example, the following inputs output this:

  1. Input: (Enter)

    Output: > (This is good)

  2. Input: (space)(Enter)

    Output: >> (This is not good)

  3. Input: q(Enter)

    Output: ERROR. Select an operation from the above menu by typing its corresponding letter.

    (>>) (This outputs two lines as intended, but because there are two characters being inputted, it also outputs 2 arrows on the next line)

  4. Input: qq(Enter)

    Output: ERROR. Select an operation from the above menu by typing its corresponding letter.

    (>)

    ERROR. Select an operation from the above menu by typing its corresponding letter.

    (>>)

The only solution that comes to mind is to change "char choice" into a string instead, so that I can limit the length of each input to one character. However, I'm aware that strings are not a data-type in C++ and cannot be used in switches. Do I need to print as string, but convert the value to char for use in the switch? Even then I don't know if that would prevent the dual input caused by inputting a letter and the subsequent newline/return.

What are my options here?

  • 2
    [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) – Jason Mar 20 '23 at 06:17
  • 1
    @ChipOnAStick My first instinct is to say don't sweat this stuff. Input validation is tedious and pretty much unnecessary in newbie programs, there are more important things to worry about. However the simplest approach seems to be 1) read a line of input as a string 2) remove leading and trailing whitespace 3) if the string is now a single character then switch on that character (as you are currently doing) 4) if not then report an error and repeat from step 1 – john Mar 20 '23 at 06:32
  • Concerning the unexpected output of `>>`: If you input `q`[ENTER], you get two characters in your input buffer. However, the `cin` is not left until you hit [ENTER]. (This is how this blocking input works.) Hence, when you leave the `cin` your loop is iterating two times: once for `q` and once for [ENTER] (without waiting in the next call of `cin >> std::noskipws >> choice;` for further manual input). And, now, (surprise) look again at the line above of `cin`... ;-) – Scheff's Cat Mar 20 '23 at 07:09
  • 1
    If you want to use lines of input terminated by new lines then use `std::getline` and parse the resulting string rather than reading character's directly – Alan Birtles Mar 20 '23 at 07:29
  • 1
    @Scheff'sCat I was certainly able to understand that after the code was written. I just didn't have the foresight to see how that code was going to impact my project when I first started writing it. Though, you bring up a good point. The code will loop for every character I enter on that line. So if I instead store the user's input as a string and reduce the maximum stored characters of that string to 1, will it no longer loop for each character? I would think not. – ChipOnAStick Mar 20 '23 at 07:36
  • @AlanBirtles Thank you. I should be able to clear any white space on the line using std:ws, then reduce the length of the resulting string with variable.length(1), correct? – ChipOnAStick Mar 20 '23 at 07:42
  • What should be done is using the right tool for the right job: if the goal here is to read a line of input from `std::cin` then that's exactly what `std::getline` is for. That's not what `<<` does. All of these contortions with `std::noskipws`, `std::ignore` and the rest of the tortured logic are due to trying to put the square `<<` peg into the round `std::cin` hole. – Sam Varshavchik Mar 20 '23 at 11:39

1 Answers1

-1

In C++ IO and especially IO error checking is notoriously difficult.

I would recommend to implement a small function.

In this function you may run a loop, until you have received exactly one alpha letter. For that you may use the save std::getline function and then check the content of a complete line.

  • If the string is empty, then just "Enter" was pressed.
  • If the string contains exactly one alpha letter, then everything is OK
  • Other characters, like "digits" or longer strings will be rejected.

At the end of the function we can convert the alpha character to always return a lowercase letter.

This makes life easier int the switch statement. No need to check for 'A' and 'a'.

In "main", you can call simply your function, to always get a correct letter.

Please see the following example code:

#include <iostream>
#include <string>
#include <cctype>

char getLowerCaseLetter() {

    // The letter that we will return
    char letter{};

    // Rund loop as long as we do not have a vild letter
    bool validLetter{};
    while (not validLetter) {

        // Read a complete line and skip all leading spaces
        std::string line{};
        if (std::getline(std::cin, line)) {

            // Just "enter" will do nothing
            if (line.empty()) {
                std::cout << "> ";
            }
            else {
                // If we found one alpha letter, then OK
                if (line.length() == 1 and std::isalpha(line.at(0))) {
                    validLetter = true;
                    letter = line.at(0);
                }
                else {
                    std::cout << "\nInvalid Input. Please enter 1 alpha letter and press Enter\n\n> ";
                }
            }
        }
        else std::cerr << "\n***Unexpected IO Error\n";
    } // Always lower case
    return std::tolower(letter);
}

int main() {
    bool runProgram{ true };
    while (runProgram) {

        // Give information to user
        std::cout << "\n\n\nOperations:\n\ta)\n\tb)\n\tc)\n\tx) to quit\n\n> ";

        // Get users choice
        switch (getLowerCaseLetter()) {
        case 'a':
            std::cout << "\nEntered 'a'\n";
            break;
        case 'b':
            std::cout << "\nEntered 'b'\n";
            break;
        case 'c':
            std::cout << "\nEntered 'c'\n";
            break;
        case 'x':
            std::cout << "\nEntered 'x'. Leaving program . . .\n";
            runProgram = false;
            break;
        default:
            std::cout << "\nInvalid selection, Please try again\n";
            break;
        }
    }
}
A M
  • 14,694
  • 5
  • 19
  • 44
  • That's brilliant. The closest I came to solving this was by first storing as a string and converting the users input for the switch by using a char, who's value is the first character of the string variable. It worked just fine except, it would ignore newline/return as a character for some reason. Thank you very much. – ChipOnAStick Mar 20 '23 at 19:47