0

Context: I am working in a chess project, i want to do input validation in the cin>>bearing operation.

Question 1: is this a good way to do input validation?

Reasons: it makes easier to read what the user has to input and makes easier to write single lane input checker with error message (as showed in the main function).

Question 2: how do i do it?

Possible duplicate: How can we check for invalid input into an overloaded operator?

What's different from that post?: i don't want the overhead caused by raising an exception and catching it (also, i hope it isn't necessary)

This is the code I try to make it work.

#include <iostream>

using namespace std;
struct Bearing
{
  unsigned x{};
  unsigned y{};
  Bearing() = default;
  Bearing(unsigned t_x, unsigned t_y) : x{ t_x }, y{ t_y } {}
  Bearing(char t_c, unsigned t_y) : x{ static_cast<unsigned>(t_c - 'a') }, y{ t_y - 1 } {}
};

std::istream &operator>>(std::istream &is, Bearing &bearing)
{
  char c;
  unsigned n;
  if (is >> c) {
    c = tolower(c);
    if ('a' <= c && c <= 'h' && is >> n && 1 <= n && n <= 8) {
      // bearing = Bearing(c, n);
      bearing = Bearing{ c, n };
      return is;
    }
  }
  is.setstate(ios_base::failbit);// register the failure in the stream
  return is;
}

int main()
{
  std::cout << "Input the piece's letter and number:\n";
  Bearing bearing;
  while (!(cin >> bearing)) { cout << "wrong input, try again\n"; }

  return 0;
}

What happens: It never waits for input again in the loop If i give it a wrong input (becomes a while true loop).

What I expect: to wait for input in each iteration if the input was not the expected.

  • 1
    If you have **trusted input**, you do not need to do input validation. If you have **untrustworthy input** (or if you are paranoid, like me) you probably ought to do input validation. – Eljay Dec 27 '22 at 15:28
  • my own input can be untrustworthy sometimes. Sometimes my programmer brain tries to input 0 as 1 when picking the row. :( – Marcus Mors Dec 27 '22 at 15:36
  • The rule of thumb I always work by for input validation is "as early as possible", and often find that an `operator>>()` function is too late for validating input- best case it can't be done any later, worst case it is too late. So, I often read a full line of input as a string (e.g. `std::getline()`), do as much validation as possible on the string itself. Then, if it is appropriate to use an `operator>>()`, I construct an `std::istrstream` from the (possibly sanitised) string. A consequence is that there often is some, but not much validation, that needs to be done in the `operator>>()` – Peter Dec 27 '22 at 15:51
  • If you use `failbit` to set the state, you must manually clear the state and ignore previous input after seeing a bad input. – Ranoiaetep Dec 27 '22 at 16:00
  • 1
    @Ranoiaetep it worked! i found this other code that asks what cin.clear does https://stackoverflow.com/questions/5131647/why-would-we-call-cin-clear-and-cin-ignore-after-reading-input and used it in my input validation. Thank you all for answering :D – Marcus Mors Dec 27 '22 at 16:07
  • @Peter i was wondering about your advice and found that `std::istrstream` is [deprecated](https://en.cppreference.com/w/cpp/io/istrstream) since c++98, why would someone use it over `std::istringstream`? (i don't mind being compatible with old c++ versions) – Marcus Mors Jan 03 '23 at 05:15
  • @MarcusMors I wrote that previous comment quickly. Use `std::istringstream` unless writing for pre-standard C++. While most people wouldn't need to write code for pre-standard C++ today, using `std::istrstream` would still be relevant when maintaining some older code (dating from before the first C++ standard was ratified in 1998). – Peter Jan 03 '23 at 09:40

1 Answers1

0

As @Ranoiaetep said, "the code needs to clean the state and ignore previous input after a bad input"

#include <iostream>
#include <limits>

int main()
{
  std::cout << "Input the piece's letter and number:\n";
  Bearing bearing;
  while (!(cin >> bearing)) {
    cout << "wrong input, try again\n";
    cin.clear();
    cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  }
  return 0;
}