Input Streams

A detailed introduction to C++ Input Streams using 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.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

Input streams are the fundamental, low-level concept in C++ that allows us to read data into our program. Use cases include the following:

  • Reading input from a file on the user's hard drive
  • Getting data from a hardware device, such as a controller or sensor
  • Parsing user input from a command-line interface
  • Communicating with other computers over the internet, or another network connection

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 from
  • std::stringstream - a bidirectional string stream, which we can both read from and write to

In 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

Input Stream State and Errors

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 stream
  • failbit - The previous operation failed in a way that did not compromise the stream. For example, an attempt to extract some output failed
  • badbit - 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 failed

It 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

Getting Input Position using 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:

  • We have reached the end of the input stream - there is nothing more to read. That is what happened in the previous example
  • The underlying stream we're reading from doesn’t support tellp(). This is extremely uncommon.

Setting Input Position using 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 stream
  • std::ios::cur - set the position relative to the current position
  • std::ios::end - set the position relative to the end of the stream

Below, 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

Getting Content using 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

Getting a Character using 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

Checking the next Character using 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

Detecting End of File (EOF) using 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

Example: Creating and Updating Objects

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.

Summary

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.

Main Points Covered

  • Basic input operations with std::cin and the limitations of using the >> operator.
  • The role of std::istringstream in reading data from string-based sources.
  • Understanding and managing stream state flags for error handling.
  • Manipulating and querying the stream's position using tellg() and seekg().
  • Reading lines and characters from streams with std::getline() and get().
  • The use of peek() for previewing stream data without extraction.
  • Constructing and initializing objects directly from stream data, demonstrating the practical application of input streams in building dynamic collections of objects.

Was this lesson useful?

Next Lesson

Working with the File System

Create, delete, move, and navigate through directories and files using the standard library's filesystem module.
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Input Streams

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.

A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Strings and Streams
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access

This course includes:

  • 124 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Working with the File System

Create, delete, move, and navigate through directories and files using the standard library's filesystem module.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved