C++ user input restriction with proper retry without “goto”

前端 未结 5 686
Happy的楠姐
Happy的楠姐 2021-01-15 14:01

I have the following code:

qstn:
  cout << \"Input customer\'s lastname: \";
  getline(cin, lname);

  if (lname.find_first_not_of(\"abcdefghijklmnopqr         


        
5条回答
  •  囚心锁ツ
    2021-01-15 14:26

    Here's an idiom I like to use:

    int i;
    
    if (std::cin >> prompt("enter an integer: ", i))
    {
        std::cout << "Read user input: " << i << "\n";
    } else {
        std::cout << "Input failed (too many attempts). Eof? " << std::boolalpha << std::cin.eof() << "\n";
    }
    

    Here, prompt is a smart input manipulator, that takes care of handling parse errors or stream failures and retrying.

    It's quite generic so actually do many things, but you don't need to indicate all the options. When the manipulator is inserted into the stream it relays to the do_manip member:

    template 
    friend std::basic_istream& operator>>(std::basic_istream& is, checked_input& manip) {
        return manip.do_manip(is);
    }
    

    The do_manip handles all the logic without any gotos :) :

    std::istream& do_manip(std::istream& is) {
        auto attempt = [this] { return infinite() || retries_ > 0; };
    
        while (attempt()) {
            if (!infinite())
                retries_ -= 1;
    
            prompter_(out_);
    
            if (is >> value_) {
                if (!run_validators(out_))
                    is.setstate(is.rdstate() | std::ios::failbit);
                else
                    break;
            } else {
                out_.get() << format_error_ << "\n";
            }
    
            if (attempt()) {
                is.clear();
                if (flush_on_error_)
                    is.ignore(1024, '\n');
            }
        }
    
        return is;
    }
    

    You can see that there is a possibility to have validations run before accepting the input.

    Here's a somewhat full-blown demo:

    Live On Coliru

    int main() {
        using namespace inputmagic;
    
        int i;
    
        if (std::cin >> prompt("enter an integer: ", i)
                .retries(3)
                .flush_on_error(false)
                .format_error("I couldn't read that (Numbers look like 123)")
                .output(std::cerr)
                .validate([](int v) { return v > 3 && v < 88; }, "value not in range (3,88)")
                .validate([](int v) { return 0 == v % 2; })
                .validate([](int v) { return v != 42; }, "The Answer Is Forbidden")
                .multiple_diagnostics())
        {
            std::cout << "Read user input: " << i << "\n";
        } else {
            std::cout << "Input failed (too many attempts). Eof? " << std::boolalpha << std::cin.eof() << "\n";
        }
    }
    

    You can see it will only accept valid integers

    • that are >3 and <88,
    • that are even
    • except 42 (forbidden number)

    When entering the numbers 21, 42 and 10 on subsequent retries, you get: live

    enter an integer: 21
    Value not valid
    enter an integer: 42
    The Answer Is Forbidden
    enter an integer: 10
    Read user input: 10
    

    However, if you enter 1 all the time you get this: live

    enter an integer: 1
    value not in range (3,88)
    Value not valid
    enter an integer: 1
    value not in range (3,88)
    Value not valid
    enter an integer: 1
    value not in range (3,88)
    Value not valid
    Input failed (too many attempts). Eof? false
    

    Or if you read from a single line file: live

    enter an integer: value not in range (3,88)
    Value not valid
    enter an integer: I couldn't read that (Numbers look like 123)
    enter an integer: I couldn't read that (Numbers look like 123)
    Input failed (too many attempts). Eof? true
    

提交回复
热议问题