std::cin
and std::istringstream
. Starting from the basics and progressing up to advanced use cases including creating collections of custom objects from our streams.Input streams are the fundamental, low-level concept in C++ that allows us to read data into our program. Use cases include the following:
In this section, we will explore the basics of input streams in C++ and how to work with them effectively.
std::istream
and std::cin
The base class for input streams is std::istream
. This is an alias for std::basic_istream
and is available by including <istream>
.
Typically, we include <iostream>
instead, which combines basic input streams and output streams.
#include <iostream>
An example of an input stream is std::cin
, which is a stream designed to get input from the terminal/console. This is the same place output from std::cout
 goes.
We obtain content from an input stream using the >>
operator, followed by a location to save that input. This will typically be some form of a string:
#include <iostream>
#include <string>
int main() {
std::string Input;
std::cout << "Please provide input: ";
std::cin >> Input;
std::cout << "Input extracted: " << Input;
}
When running this program, it will pause and ask us for input. Entering an input and pressing return will cause it to be echoed back to us.
Please provide input: Hello
Input extracted: Hello
Note that the >>
operator extracts one "token" of input. It treats spaces as the token separator. As such, if our input contained spaces (eg, multiple words), only the content before the first space would be extracted.
Please provide input: Hello World
Input extracted: Hello
Later in the lesson, we cover different ways to grab input, so we can extract exactly what we want.
std::istringstream
In the previous lesson, we covered string streams and focused on their use as an output stream
String streams can serve as input streams too. We set this up by creating an object of one of the following types:
std::istringstream
- an input string stream, which we can read content fromstd::stringstream
- a bidirectional string stream, which we can both read from and write toIn the following example, we create a std::istringstream
and use it to read content into a string:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Stream{"Hello World"};
std::string Extract;
Stream >> Extract;
std::cout << "Input extracted: " << Extract;
}
Input extracted: Hello
As we work with and run operations on our streams, they maintain an internal collection of flags (a bitmask) representing the state of the stream. The three main flags are as follows:
eofbit
- The end of the input has been reached. "eof" refers to "end-of-file", but applies to any input streamfailbit
- The previous operation failed in a way that did not compromise the stream. For example, an attempt to extract some output failedbadbit
- A previous operation failed in a way that may have left the stream in a state we weren’t expecting. For example, an attempt to insert some input failedIt is a good habit to check our streams for any issues after each read. Four methods might be helpful:
#include <sstream>
int main() {
std::istringstream Stream;
if (Stream.good()) {
// No bits set
}
if (Stream.fail()) {
// failbit or badbit set
}
if (Stream.bad()) {
// badbit set
}
if (Stream.eof()) {
// eofbit set
}
}
Additionally, we can coerce our stream to a boolean. For example, within an if
statement, we can check if our stream is in a good state:
if (Stream) {
// All good
}
This is equivalent to the expression !Stream.fail()
- it will return false
if either failbit
or badbit
is set.
The approach we should use to monitor the state of our stream depends on how our stream is being used.
Below, we try to extract three words from a stream that only has two. Remember, the >>
operator returns a reference to our stream, so our stream is getting passed to each if
statement, to be coerced to a boolean in the way described above:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Stream{"Hello World"};
std::string Extract;
if (Stream >> Extract) {
std::cout << "Input extracted: " << Extract
<< '\n';
}
if (Stream >> Extract) {
std::cout << "Input extracted: " << Extract
<< '\n';
}
if (!(Stream >> Extract)) {
std::cout << "Extraction failed";
}
}
Input extracted: Hello
Input extracted: World
Extraction failed
Below, we compress this into a loop. This is a common pattern for reading from an input:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Stream{"Hello World"};
std::string Extract;
while (Stream >> Extract) {
std::cout << "Input extracted: " << Extract
<< '\n';
}
}
Input extracted: Hello
Input extracted: World
tellg()
Similar to output streams, our input streams have a position. The position represents where the next input will come from, once we execute a read operation.
We can find out what the position is using the tellg()
 method:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello World"};
std::string Extract;
std::cout << "Input Position: "
<< Input.tellg();
Input >> Extract;
std::cout << "\n\nExtracted: " << Extract;
std::cout << "\nInput Position: "
<< Input.tellg();
Input >> Extract;
std::cout << "\n\nExtracted: " << Extract;
std::cout << "\nInput Position: "
<< Input.tellg();
}
Input Position: 0
Extracted: Hello
Input Position: 5
Extracted: World
Input Position: -1
When tellg()
fails, it returns -1
. The most common causes for this are:
tellp()
. This is extremely uncommon.seekg()
We can change the input position using seekg()
. We can set the position to an absolute value by passing a single number:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello World"};
std::string Extract;
Input.seekg(5);
Input >> Extract;
std::cout << "Extracted: " << Extract;
Input.seekg(1);
Input >> Extract;
std::cout << "\nExtracted: " << Extract;
}
Extracted: World
Extracted: ello
We can also offset the position from some anchor, by passing two arguments: a numeric offset, and the anchor to use. We have three options for the anchor:
std::ios::beg
- set the position relative to the beginning of the streamstd::ios::cur
- set the position relative to the current positionstd::ios::end
- set the position relative to the end of the streamBelow, we show some examples of this:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"123456789"};
std::string Extract;
// Seek to 2 positions after the stream start
Input.seekg(2, std::ios::beg);
Input >> Extract;
std::cout << "Extracted: " << Extract;
// Seek 2 positions back from the current
Input.seekg(-2, std::ios::cur);
Input >> Extract;
std::cout << "\nExtracted: " << Extract;
// Seek to 5 positions before the end
Input.seekg(-5, std::ios::end);
Input >> Extract;
std::cout << "\nExtracted: " << Extract;
}
Extracted: 3456789
Extracted: 89
Extracted: 56789
std::getline()
The std::getline()
function, available within <string>
, will extract a line of text from an input stream, and insert it into a std::string
#include <iostream>
#include <string>
int main() {
std::string Input;
std::cout << "Please provide input: ";
std::getline(std::cin, Input);
std::cout << "Input extracted: " << Input;
}
Please provide input: Hello World
Input extracted: Hello World
The std::getline()
function returns a reference to the input stream, which we can use as needed. Below, we replicate our earlier example of extracting input in a loop, until the input stream fails its boolean check:
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::istringstream Input{"Hello\nWorld"};
std::string Extract;
while (std::getline(Input, Extract)) {
std::cout << "Extract: " << Extract << '\n';
}
}
Extract: Hello
Extract: World
By default, std::getline()
reads the steam input line by line, that is, it uses line break characters such as \n
as delimiters.
However, we can specify the delimiter we want to use by passing it as the third argument.
Below, we extract "lines" of input, where lines are separated by commas:
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::istringstream Input{"Hello,World"};
std::string Extract;
while (std::getline(Input, Extract, ',')) {
std::cout << "Extract: " << Extract << '\n';
}
}
Note - the delimiter is not included in the extract. Additionally, within the stream, the input position is moved past the delimiters, effectively skipping over them.
As such, the delimiters are never included in the text we extract. The previous example has the following output:
Extract: Hello
Extract: World
get()
An alternative to the std::getline()
function is the get()
function, which is a method on input streams. By default, get()
extracts a single character:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello"};
char Extract;
Input.get(Extract);
std::cout << "Extract: " << Extract;
}
Extract: H
Similar to other methods, it returns a reference to the stream. Below, we use this to control a loop that extracts everything from the input stream, character by character.
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello"};
char Extract;
while (Input.get(Extract)) {
std::cout << "Extract: " << Extract << '\n';
}
}
Extract: H
Extract: e
Extract: l
Extract: l
Extract: o
The get()
method also accepts a second argument, which allows us to extract multiple characters. When using this, we typically write the extract to a c-style string - a character array, or char*
Given this is a C-style string, we need to leave an additional space for the null termination character, which denotes where the string ends.
Below, we pass 4
to get()
. This causes it to write three characters from our stream to our char*
. with the 4th character being reserved for the null terminator.
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello"};
char Extract[4];
Input.get(Extract, sizeof(Extract));
std::cout << "Extract: " << Extract;
}
Extract: Hel
Similar to std::getline()
, we can pass an additional argument to get()
, which will act as a delimiter. The get()
method will extract characters until it reaches the limit specified by the second argument, or it encounters the delimiter specified by the third argument.
However, unlike std::getline()
, the input position is not moved past the delimiter:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello,World"};
char Extract[100];
Input.get(Extract, sizeof(Extract), ',');
std::cout << "Extract: " << Extract;
// Note: the input position remains before the
// comma, so future reads will include it
std::string Remainder;
Input >> Remainder;
std::cout << "\nRemainder: " << Remainder;
}
Extract: Hello
Remainder: ,World
peek()
The peek()
method allows us to look at the next character in the stream, without necessarily extracting it.
Below, we peek at the next character in the stream, but the input position is not moved forward. Therefore, when we come to extract the input later, the character we peeked at is included.
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello"};
std::string Extract;
std::cout << "Next Value: " << Input.peek();
Input >> Extract;
std::cout << "\nExtracted: " << Extract;
}
The compiler does not inherently know the type of data that is in our stream - it just sees bytes. When we extract something to a char
, for example, the compiler understands it should interpret the byte as a character.
But our use of peek()
here does not give the compiler any such hint, So the byte we peeked at is being interpreted as a number:
Next Value: 72
Extracted: Hello
In this case, 72
is the ASCII code for an uppercase 'H'
.
We can cast it to a character to get the expected output:
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello"};
std::string Extract;
std::cout << "Next Value: "
<< static_cast<char>(Input.peek());
Input >> Extract;
std::cout << "\nExtracted: " << Extract;
}
Next Value: H
Extracted: Hello
peek()
A common use of peek()
is to check for the end of the input. This is called the end of file, even for input streams that are not files.
Earlier, we covered that input streams have an eof()
method, that returns true if the end of input was detected. However, the stream may have no further input, but the eof
flag has not yet been set.
Often, this is caused by trailing delimiters. For example, calling getline()
on a stream containing "Hello\n"
will extract "Hello", but will not set the end-of-file flag.
This is because getline()
stopped once it reached the new line \n
character. It didn’t look past it to notice the end of the file.
#include <iostream>
#include <sstream>
int main() {
std::istringstream Input{"Hello\n"};
std::string Extract;
std::getline(Input, Extract);
std::cout << "Extracted: " << Extract;
if (!Input.eof()) {
std::cout << "\nEOF is not true";
}
}
Extracted: Hello
EOF is not true
In this scenario, attempting a further read would fail, and set both the eofbit
and failbit
on our stream. This is not necessarily a problem - our surrounding loop and logic may be able to accommodate that.
However, in some situations, we will need to check whether we reached the end of file before even attempting a read. The peek()
method can help us here.
Below, we extract Hello
and World
from our stream using getline()
. There is no further usable input but, because the data has a trailing newline character, our read operations have not yet found the end of the file.
The peek()
method will not advance the input position, but it will set the state of our stream based on what it encounters.
In the following example, our peek()
operation sees the end of the file, which causes the stream’s state to be updated.
#include <iostream>
#include <sstream>
#include <string>
int main() {
// Note the trailing comma
std::istringstream Input{"Hello\nWorld\n"};
std::string Extract;
std::getline(Input, Extract);
std::cout << "Extracted: " << Extract;
std::getline(Input, Extract);
std::cout << "\nExtracted: " << Extract;
if (!Input.eof()) {
std::cout << "\nWe have not reached EOF...";
}
Input.peek();
if (Input.eof()) {
std::cout << "\n...but now we have";
}
}
Extracted: Hello
Extracted: World
We have not reached EOF...
...but now we have
The main use case for streams is to bridge the gap between data and objects within our program.
For example, the user may have files from our application stored on their system, or data coming in from the internet or another network.
We need to use that data to create C++ objects that we can interact with in the normal way.
Below, we’ll use an istringstream
to simulate the data source, but the process is the same across all input streams. In the next lesson, we’ll cover file systems and networking.
Let's create a character based on raw data coming from an input stream. In this example, we’re able to create a full-fledged C++ object using the plain text input "Legolas 80 1"
 :
#include <iostream>
#include <sstream>
using stream = std::istringstream;
class Character {
public:
Character(stream& Stream) {
Stream >> Name;
Stream >> Level;
Stream >> isAlive;
}
std::string Name;
int Level;
bool isAlive;
void Log() {
std::cout << Name << " - Level " << Level
<< (isAlive ? " (Alive)"
: " (Dead)")
<< '\n';
}
};
int main() {
stream Input{"Legolas 80 1"};
Character Player{Input};
Player.Log();
}
Legolas - Level 80 (Alive)
Given that streams keep track of their input position, we can use the same stream to create a dynamic number of objects:
#include <iostream>
#include <sstream>
#include <vector>
using stream = std::istringstream;
class Character {/*...*/}
int main() {
stream Input{
"Legolas 80 1 Gimli 70 1 Aragorn 60 0"};
std::vector<Character> Party;
while (Input.good()) {
Party.emplace_back(Input);
}
for (auto& Member : Party) {
Member.Log();
}
}
Legolas - Level 80 (Alive)
Gimli - Level 70 (Alive)
Aragorn - Level 60 (Dead)
Working with streams in this way is the key that unlocks many new capabilities. This can include basic functionality like saving a file to the hard drive, and then using that file to restore our program to that state when it’s next opened.
But it’s also the underlying concept that enables us to share the state of objects across multiple machines, running our program at the same time over the internet.
In the next chapter, we’ll build upon these fundamentals, to create architectures that allow us to ramp up the complexity to deal with these more advanced use cases.
We’ll show how to read and write streams from more useful sources, including the hard drive and the internet.
We’ll also introduce different ways to structure the data in the streams, to make it more manageable or performant. We'll first look at JSON, a more versatile and human-readable way of storing and transmitting information:
[{
"name": "Legolas",
"level": 10,
"isAlive": true,
}, {
"name": "Legolas",
"level": 10,
"isAlive": true,
}, {
"name": "Legolas",
"level": 10,
"isAlive": false,
}]
And we’ll also go in the opposite direction, working with binary streams that are not readable by humans at all, but instead focus on maximizing performance. These are the type of streams we use when creating things like fast-paced multiplayer games.
We’ll also introduce third-party libraries that build on top of streams, abstracting away much of the low-level concerns we’ve covered so far, and allowing us to build our projects more quickly.
This lesson has taken you through the fundamentals of input streams, from basic input with std::cin
to advanced stream manipulation with tellg()
, seekg()
, and beyond.
We've explored how streams maintain their state, handle errors, and can be used to construct complex objects from incoming data, setting the stage for working with file and network streams in real-world applications.
std::cin
and the limitations of using the >>
operator.std::istringstream
in reading data from string-based sources.tellg()
and seekg()
.std::getline()
and get()
.peek()
for previewing stream data without extraction.A detailed introduction to C++ Input Streams using std::cin
and istringstream
. Starting from the basics and progressing up to advanced use cases including creating collections of custom objects from our streams.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.