1

The scenario is: input integers and characters alternatively and read them into variables of the right type. I took the straightforward approach: read anyways as if they were all integers; when failed, reset cin flags and read as characters.

int a; char c; while (true) { cin >> a; if (cin.fail()) { if (cin.eof()) { cout << "eof" << endl; break; } cin.clear(); cin >> c; cout << "fail: " << c << ": " << int(c) << endl; } else { cout << "success: " << a << endl; } } 

It works just fine except for the very two characters: +, -. For instance, when you input 2 4 + 5 *, the output would be:

success: 2 success: 4 fail: 5: 53 fail: *: 42 eof 

For *, it works; but for +, it seems that + is lost and 5 is read instead. I did some tests myself (e.g. using cin.get() instead) and found that: after cin >> a failed when encoutering +, + is consumed and the stream position points at the space between + and 5.

What makes + and - special? After some researches, I think the reason is possibly that: when cin expects an integer, the + or - it encounters is accepted as a possible prefix of integers like +5, -3, etc. This leads to the fact that + is consumed, rather than remains in the input stream like other characters.

How do you interpret the problem? How to fix the code to make it function properly?

PS: I know that one alternative solution is to read them all as strings and then do further parsing. I'm just curious whether it's possible to modify it based on the original approach. This way I could be more clear about the machanisms behind cin.


I think this post can be closed now. Thanks for all the comments and answers. The opinions and advice you shared are greatly appreciated, which I learned a lot from.

As I've said, it's not about practical use and I'm not looking for a well-rounded solution in the first place. In practice I will surely take the string-based approaches as suggested by you. It's just a small problem I ran into, after which I decided to take a short digression and play around the issue a little bit.

As for the question I raised, i.e. fixing the code, I've learned some from the answers below and official Docs. Using functions like cin.putback() cin.peek() cin.tellg() cin.seekg() will help tackle the problem.

10
  • 2
    I know that one alternative solution is to read them all as strings and then do further parsing. This is the preferred way to handle user input Commented Apr 10 at 15:36
  • Your explanation about prefixes is correct (how else could formatted input work?). The correct way to do this is to read everything as a string (that cannot fail), and then analyse the string and if it is in the form of an integer convert it. Commented Apr 10 at 15:40
  • 1
    Also consider this input 123abc. Do you consider that to be an integer followed by a string, or, because there is no space, a single string? Your approach will choose the former interpretation, but most users I think would choose the later. Commented Apr 10 at 15:42
  • When this code cin >> a; reads + 5 it will extract + then will encounter space and report an error, since only digits are expected. + without digits can't be interpreted as a number. After you have clear the error you do cin >> c; which will ignore space (which lead to an error) and then character 5 is extracted. Basically + is not put back into a stream after later failure is encountered. Commented Apr 10 at 15:52
  • 1
    I would recommend drop direct use of streams and just use some parsing library. Boost spirit is quite nice. Commented Apr 10 at 15:54

2 Answers 2

2

The comments to your question show that there are much more to think than the mere "+/-" issue.

Yet as to tackle this issue, your analysis is correct in the sense that these characters can be interpreted as the beginning of an int and got consumed.

A workaround for this issue only is to parse char by char and not by int and to handle specifically + and -:

int a; char c; while (true) { char ch; if (std::cin >> ch) { if (ch == '+' || ch == '-') { // Consume the special character and continue std::cout << "special char: " << ch << '\n'; continue; } else { // Try to read the character as an integer std::cin.putback(ch); if (std::cin >> a) { std::cout << "success: " << a << '\n'; } else { std::cin.clear(); std::cin >> c; std::cout << "fail: " << c << ": " << int(c) << '\n'; } } } else if (std::cin.eof()) { std::cout << "eof" << '\n'; break; } else { std::cin.clear(); std::cin >> c; std::cout << "fail: " << c << ": " << int(c) << '\n'; } } 

Take notice of the usage of putback that allows to "unconsume" +or - and then try to read an int.

input:

24+5

output

success: 24
special char: +
success: 5

But remember: it's only a quick workaround for this single issue but parsing a possibly ill -formed expression will raise many other issue that won't be solved by merely elaborating upon this fix.

Sign up to request clarification or add additional context in comments.

Comments

1

Yes, + and - can be part of integers when reading from a stream. You can verify that by means of a simpler example

#include <sstream> #include <iostream> int main() { int x; std::stringstream inp{"+3-5"}; while (inp >> x) std::cout << x << "\n"; } 

The output is:

3 -5 

For your task I suggest to keep handling of the stream seperate from the parsing logic. If you can afford a little extra memory and allocations, read all input first. Iterating a string is simpler than deliberately running into an error state of the stream and resetting it.

#include <iostream> #include <string> #include <vector> int main() { std::string input; std::getline(std::cin,input); std::vector<int> ints; std::vector<char> others; for (const auto& c : input) { if (c == ' ') continue; if (std::isdigit(static_cast<unsigned char>(c))) ints.push_back(c-'0'); else others.push_back(c); } for (const auto& e : ints) std::cout << e << " "; std::cout << "\n"; for (const auto& e : others) std::cout << e << " "; } 

Live Demo

Not using the stream extraction to parse integers comes at the price of using std::isdigit and a conversion from char to int (by subtracting '0'). I assume you want to read single digit integers. Reading larger numbers can be done by consuming more characters from the string with isdigit(c)!=0 at once.

3 Comments

The function call std::isdigit(c) can be dangerous, because calling the function std::isdigit with a negative argument value will invoke undefined behavior, unless that value is equal to EOF (which is usually -1). See this Stack Overflow answer to another question for further information. Writing std::isdigit(static_cast<unsigned char>(c)), or using the C++ localization library version of the function, would be safer.
@AndreasWenzel fixed
Yes, it looks good now. I will not delete my comment, because it will probably be useful for other readers who are wondering why you are using static_cast.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.