C++ Logo


Advanced search

Input should come from the right hand side

From: Bill Kerney <bkerney_at_[hidden]>
Date: Tue, 8 Sep 2020 05:04:42 +0000
Hi all,

One ongoing problem I've had with the basic I/O functionality of C++ is that input tends to take place in a call-by-reference manner, such as with cin in iostream.h.

This design conflicts with a lot of how Modern C++ works, such as:
1) You can't use it with auto
2) You can't use it with const
3) You have to declare a variable and then initialize it on a separate line which means you either have an uninitialized variable briefly in your code or you waste a default initialization on it to be safe

There's other, related, problems with basic I/O in C++, such as:
4) Mixing cin and getline tends to result in bugs due to the difference in how they process newlines
5) How iostreams throws errors (or doesn't throw, specifically) could probably be done better nowadays
6) Retrying on an error is too complicated for new programmers.

The C++ experience for new programmers is something I care a great deal about, so the #6 was the reason why I wrote a short header file (~150 LOC) that moves input to the right hand side and handles errors and retrying in a more modern way, with the philosophy of having common things be as simple as possible for the end user. You can look at it here: https://github.com/ShakaUVM/read

Suppose you want the user to input their name, but you want the name to be immutable past the point of entry because you're one of those const-always folks. You could do it like this right now:
string temp;
cin >> temp;
const string name = temp;

Which is just really verbose and awkward, and leaves temp lying around.

Using my read library, it would look like this:

const string name = read();

Easy. Breezy. Beautiful. Since the variable is declared on the same line it gets the input it can be const, and you don't need to split what should be a simple statement into three statements with a move or copy thrown in for good measure.

Or if you're one of those Auto Almost Always folks, you're kind of toast with the current system, since you can't combine auto with call by reference input. If input comes from the right hand side, though, then you can do things like this:

auto x = read<myClass>();

Where myClass is a class you've written with an operator>> defined on it. Read sits on top of iostream, and so any class with an operator>> defined can be read via the read library. This may or may not be a good design decision, but it does make it backwards compatible. With the read library, it can deduce the type of read to be done from the left hand side, but if you use auto, you need to specify the type as a template parameter to it. It is still clean enough to be taught to new students.

What about error handling?

Right now, let's say you want to read an int from a stream, but there's the possibility you don't get an int. iostreams mandate you check for errors after every operator, so many people don't. Especially new programmers. So their code starts falling through all their input, and this usually leads to catastrophic failure.

There's two options for handling errors in the read library. I thought about using exceptions, but I don't like using exceptions for routine errors, so the two options are:
1) ignore the error and keep reading until you get the type asked for. So if you do a:
int x = read(stream);
...it will keep reading from the stream, discarding bad input, until an int is read.
2) Return an optional<T>. So you can test the output and see if you got the T you asked for or you get a nullopt if an error occurred. For example:
auto x = read_opt<float>(stream);
if (x) { ... }
else { ... }

Finally, it allows you to intermix my cin and getline equivalent without the usual bugs. For example:
int x;
string s;
cin >> x;
...will return an empty string into s if the user entered an int and hit enter after. Which is unexpected and baffling for something like 100% of new students.

The read equivalent looks like this:
int x = read();
string s = readline();

It handles the switch off just fine, and, IMO, looks a lot nicer as well. It also guarantees an int is read, and the error flag will be cleared and handled properly, something new programmers don't usually know how to do.

Bill Kerney

Received on 2020-09-08 00:08:12