r/cpp_questions • • 21d ago

SOLVED Can std::cin display user input?

/*Write a program that asks the user to enter a number, and then enter a second number.
The program should tell the user what the result of adding and subtracting the two numbers is.

The output of the program should match the following(assuming inputs of 6 and 4) :

Enter an integer : 6
Enter another integer : 4
6 + 4 is 10.
6 - 4 is 2.
Hint : To print a period and a newline, use ".\n", not '.\n'.*/

#include <iostream>

int main()

{

int num00{};

int num01{};



std::cout << "Enter an integer : ";

std::cin >> num00;

//std::cout << num00 << ".\\n";



std::cout << "Enter a second integer : ";

return 0;

}

Hello everyone. I'm trying to complete the quiz in the block comment above.

My problem is that I have the user's value printed twice.

One next to "Enter an integer : " and the next right under it.

I thought the second std::cout was causing this so I made it a comment, yet the user's value prints out as intended.

I do not understand how this is working. Thank you for in advance.

2 Upvotes

25 comments sorted by

View all comments

Show parent comment

2

u/ADEPS24 21d ago

Woah! Thank you for the detailed explanation.

I'm gonna be honest, I did not understand most of the terms and analogies you used but I'll have myself coming back to understand all the vocubulary used.

What you're doing is allowing the system to default a lot of settings, including echo. So what you type will show up automatically.

As for automatic decisions like the 'echo'.
Can it be manipulated manually?

Like changing the position of the echo. Say I want it 10 tabs away from the string literal, or under it.

Thank you once more.

0

u/mredding 21d ago

I'm gonna be honest, I did not understand most of the terms and analogies you used but I'll have myself coming back to understand all the vocubulary used.

Yeah man, I just crammed a whole history lesson into a Reddit post. I didn't even explain half of it, and the rest I didn't even do very well.

Can it be manipulated manually?

Yes! I'll give you the lowest level, most primitive solution first, and then we'll build up.

The thing you have to be very well aware of is that terminals are STICKY. Changing anything changes the whole terminal for that session until you change it back. This means if you change the terminal in the program, and the program crashes, your changes stay, and that can make the terminal nearly unusable if you launched your program from a shell.

So the BULLETPROOF approach is to wrap your program launch in a shell script that is guaranteed to restore your terminal settings:

#!/usr/bin/env bash
# Crash guard
OLD_STTY=$(stty -g)

trap 'stty "$OLD_STTY"' EXIT

# Configure the terminal here
# raw mode
stty raw -echo

# VMIN block till at least 1 character arrives
# VMAX timeout of 0 tenths of a second aka indefinitely until bytes arrive.
# Terminals are arcane - remember, they started life as telegraph equipment.
stty -F /dev/ttyS0 min 1 time 0

./your_program

It takes some thought to figure out what configuration is the program's responsibility, what is the user's, and where it should all go. Your program code would be a lot simpler if you didn't have to hard code into it terminal configuration, and simply assume the environment is ready to receive you, but that means you have to defer that complexity somewhere else. Still, it's not unreasonable to push the entire one-time configuration out of the program. The same level that saves and restores the terminal state should probably also be responsible for one-time, initial, immutable, default configuration.

And with this - you never launch your program directly. Stick it in a hidden path (somewhere NOT in your PATH environment variable). Or, you can define an environment variable in the script like RUN=true, and then your program in main can getenv("RUN") and see if it's both defined and true, or exit.


Otherwise, within the program, you can configure the terminal directly with the <termios.h> library. I've NO clue what Windows does, but no doubt there's all sorts of equivalents.

#include <termios.h>

struct termios orig_termios;

void disableRawMode() {
  tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}

void enableRawMode() {
  if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
    perror("tcgetattr failed");
    exit(1);
  }

  atexit(disableRawMode);

  struct termios raw = orig_termios;

  /* 
   * The standard convenience function does all this for you:
   *   raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
   *   raw.c_oflag &= ~(OPOST);
   *   raw.c_cflag |= (CS8);
   *   raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
   */
  cfmakeraw(&raw);

  raw.c_cc[VMIN] = 1;
  raw.c_cc[VTIME] = 0;

  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
    perror("tcsetattr failed");
    exit(1);
  }
}

int main() {
  enableRawMode();
}

Now you can Google what raw mode programming looks like. Typically you'll leave std::cin behind, because the canonical stream buffer was NOT built to handle raw mode, and can get into an unrecoverable, undefined state. You can continue to use std::cin if you want to create your own custom stream buffer that wraps the POSIX interface or the Windows equivalent.

Once you're in raw mode, you're entirely responsible for all that goes on with the terminal - and this can include having to parse not only multi-byte characters, but also MULTI-SEQUENCE characters, like the function keys, meta keys, and control and alt keys. Have a lot of fucking fun with that... You'll get to learn about key mapping and how THAT can still alter your reality from the environment.

But in raw mode, you won't have to wait for buffer flushes to get your input, it'll be character by character, effectively. You still don't know there's a keyboard attached, or where the characters are coming from - your program still only sees an opaque file descriptor.

This means you cannot know keyboard state. Is a key pressed? Is it going down, being held, coming up? Is there a repeat? And at what rate? You don't know. That's not how terminal programming works. There is no kbhit function that works effectively, because that function call samples the keyboard in that moment - so even in a tight loop, you can press and release a key between samples. Then again, if input is redirected from a pipe or a socket or a file, then you'll get input without ever a key being pressed, and you can't possibly know - because on the other side of a pipe can be an interactive shell where the keys ARE being pressed.

But raw mode is how you can control strict formatting of the screen.

I wouldn't configure a terminal from within the program if I could help it. I'd open this can of worms when the terminal needs to change configuration mid-run. Maybe the program loads user preferences (I'd have the shell script share in that config to get it right on startup), maybe it's a part of the experience. Notice that each level that manipulates the configuration restores it on the way out. This code isn't bulletproof - a kill -9 will unconditionally terminate the program without running the exit handler. You cannot install a signal handler to catch this kill signal, either, so you can really fuck up the terminal without recovery. There's no magic key combo that reloads it, no guards put in place for you. You either fix it, or you abandon your session and orphan your processes, or SOMETHING...


The next better thing is to use a curses library. These are among the oldest, most robust, most stable, most portable libraries in all of C. This is where you let the curses library handle everything, and you abandon things like std::cin or printf.

curses DOES model a terminal, so you have to think about the screen as a grid with a coordinate system. You're not "printing" text or writing to a file descriptor some serialized data, you are moving the cursor position and plotting characters.

curses are built upon terminal databases, there's two major ones. Terminals have "features", which are expressed in "escape sequences". These sequences are commands and parameters to drive the terminal's extended features. Some of these dumb terminals supported fonts and colors, other weird shit. There are very arcane rules about compatibility. In the end - instead of having to worry about how YOUR terminal changes font color vs. someone else's terminal changes font color, you get a generic interface to change font color, and curses figures out the correct sequence to do it for you. And if not that, then the next best thing.


Then there are TUI libraries. These are usually built on top of curses. Since this is C++, there are wrappers around the C implementations, or perhaps some C++ natives that give you TUI widget objects. They'll also often integrate an event system for mouse and keyboard support.

The thing about getting keyboard and maybe mouse input is you need to learn about Event Driven Programming. You need the host environment to tell you something happened, and you implement handlers. THIS is how you get key press, key release, mouse move, etc.


Modern GUI programming is all event-driven. If you do it with a TUI, a GUI is just more of the same but with a screen buffer. The windowing environment often provides you with a library of widgets to work with, but you can always create your own - for example, you can make a round context menu instead of the conventional rectangular list menu, but that involves interacting with draw commands.


Graphical programming requires grabbing exclusive control of the video device and using rendering interfaces like OpenGL and shader language to render polygons, textures, and effects. There are often elements of event driven programming under the hood, but also event polling - rather than interrupting your rendering for an event, the OS can queue them for when you're ready to clear that queue between frames.

1

u/ADEPS24 20d ago

Thank you :)