Introduction
Hello! In our last lesson, we built a crucial bridge between our C++ programs and the filesystem. You learned how to use ofstream to save a std::vector of Song structs to a text file, playlist.txt. We also used ifstream and std::getline to read the raw lines back, confirming our data was saved correctly.
However, reading the data as plain strings like "1971,Stairway to Heaven" is only half the battle. To make that data useful again, we need to convert it back into structured Song objects. This process is called parsing.
Today, you will learn how to parse a simple text file into a std::vector of structs. This is a fundamental skill in almost any application that deals with data, from loading user configurations and processing log files to handling datasets—something you're likely familiar with from your work in data analysis. We will complete the save/load cycle for our playlist application.
1. The Parsing Challenge
In the last lesson, we saved our data in a simple comma-separated format. Each line in playlist.txt looks something like this:
1971,Stairway to Heaven
Our goal is to read this line and populate a Song struct:
struct Song {
std::string title;
int year;
};
This involves two main steps for each line:
- Splitting the string at the comma to separate the year from the title.
- Converting the string
"1971"into an integer1971.
This is a common task when working with tabular data stored in text files, which might look like this in a spreadsheet before being exported:

2. The Right Tool: std::stringstream
To tackle this, we'll use a powerful tool from the <sstream> header: std::stringstream.
A stringstream allows you to treat a std::string as if it were a stream, just like std::cin or an ifstream file stream. This means you can use the same functions you're already familiar with—the extraction operator (>>) and std::getline()—to pull data out of a string.
This is the general workflow for parsing a file:
- Read a whole line from the file into a
std::stringusingstd::getline(file, line). - Create a
std::stringstreamfrom thatline. - Use
>>orstd::getline()on thestringstreamto extract the individual pieces of data. - Convert data to the correct types if necessary.
- Populate your struct and add it to a vector.
Let's start with a simple case where the data is separated by spaces. The following resource provides an excellent walkthrough of this exact pattern.
Parsing Table Data from Text Files in C++
The article "Parsing Table Data from Text Files in C++" from CodeSignal Learn demonstrates the core parsing pattern. It shows how to read a file line by line, use a string stream to parse each line, and store the results in a vector of structs.
Please read the sections "Understanding Text-Based Table Structure", "Starting Parsing Process", "Splitting Lines into Columns", and "Outputting the Parsed Data". Focus on how std::istringstream (which works like stringstream for input) is used inside the while loop to extract data from each line into the Person struct.
The key insight from the article is this loop:
// This is the core logic from the resource
while (std::getline(file, line)) {
Person person;
std::istringstream iss(line);
// The '>>' operator extracts space-separated values
iss >> person.name >> person.age >> person.occupation;
people.push_back(person);
}
This works perfectly for space-separated data. But what about our comma-separated playlist? If we used this method on "Stairway to Heaven", the >> operator would stop at the space and we'd only get "Stairway". We need a more precise tool.
3. Handling Delimiters and Converting Types
To handle specific delimiters like commas, we can use the version of std::getline that accepts a third argument: the delimiter character.
std::getline(stream, string_variable, delimiter_char);
This tells getline to read from the stream until it finds the delimiter.
Furthermore, when we read the year ("1971"), it will be stored in a std::string. To convert it to an int, we can use the std::stoi (string to integer) function from the <string> header.
The next resource provides a fantastic, practical example of parsing a comma-separated value (CSV) file, putting both of these techniques together.
Reading CSV Files in C++: How To Guide
The article "Reading CSV Files in C++" on Medium gives a real-world example of parsing structured data. We'll focus on the section where it parses sales data into a vector of structs.
Please read the section titled "Real-World Application: Analyzing Sales Data". Pay close attention to the readSalesCSV function. Notice how it uses std::getline with a comma delimiter on a stringstream and then uses std::stoi and std::stod to convert string fields into numbers before populating the SaleRecord struct.
The logic from that article is exactly what we need for our playlist. Here's a summary of the approach applied to our Song struct:
// This is the pattern we will use
std::string line;
while (std::getline(inFile, line)) {
std::stringstream ss(line);
std::string year_str;
std::string title;
// Read from the stringstream up to the comma
if (std::getline(ss, year_str, ',') && std::getline(ss, title)) {
// The rest of the line is the title
Song new_song;
new_song.title = title;
new_song.year = std::stoi(year_str); // Convert string to int
playlist.push_back(new_song);
}
}
The if condition cleverly chains the getline calls. Each getline call returns the stream, which evaluates to true if the read was successful. The if body only executes if both parts of the line were read correctly, making the code more robust.
4. Practical Exercise: Loading the Playlist
Now it's time to put everything together and complete our save/load functionality. You will write a load_playlist function that reads playlist.txt and parses it into a std::vector<Song>.
Instructions:
- Start with the code from the previous lesson's exercise.
- Create a new function
load_playlistthat takes one argument,const std::string& filename, and returns astd::vector<Song>. - Inside the function:
- Create an empty
std::vector<Song>to hold the results. - Open the file using
std::ifstream. Don't forget to check if it opened successfully. - Use a
while (std::getline(inFile, line))loop to read the file. - Inside the loop, use the
stringstream,getline(with a comma delimiter), andstd::stoipattern we just discussed to parse each line. - Create a
Songobject, populate it with the parsed data, andpush_backit into your vector. - After the loop, return the vector.
- Create an empty
- In
main, callload_playlistand store the result in a new vector. - Finally, loop through the new vector and print each
Song's details to the console to verify that your parsing was successful.
Here's a code skeleton to guide you:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream> // Don't forget this header!
struct Song {
std::string title;
int year;
};
// Function from previous lesson (can be used to create the file)
void save_playlist(const std::string& filename, const std::vector<Song>& playlist) {
std::ofstream outFile(filename);
if (!outFile.is_open()) {
std::cerr << "Could not open " << filename << " for writing." << std::endl;
return;
}
for (const auto& song : playlist) {
outFile << song.year << "," << song.title << "\n";
}
}
// === YOUR IMPLEMENTATION GOES HERE ===
std::vector<Song> load_playlist(const std::string& filename) {
std::vector<Song> loaded_playlist;
std::ifstream inFile(filename);
if (!inFile.is_open()) {
std::cerr << "Could not open " << filename << " for reading." << std::endl;
return loaded_playlist; // Return empty vector on error
}
std::string line;
// Your while loop and parsing logic here...
while (std::getline(inFile, line)) {
std::stringstream ss(line);
std::string year_str;
std::string title;
if (std::getline(ss, year_str, ',') && std::getline(ss, title)) {
try {
Song song;
song.year = std::stoi(year_str);
song.title = title;
loaded_playlist.push_back(song);
} catch (const std::invalid_argument& e) {
std::cerr << "Warning: Could not parse year for line: " << line << std::endl;
}
}
}
return loaded_playlist;
}
// ======================================
int main() {
std::vector<Song> original_playlist = {
{"Bohemian Rhapsody", 1975},
{"Like a Prayer", 1989},
{"Smells Like Teen Spirit", 1991},
{"Billie Jean", 1983},
{"Stairway to Heaven", 1971}
};
const std::string filename = "playlist.txt";
// 1. Save the original playlist to create the file
save_playlist(filename, original_playlist);
std::cout << "Original playlist saved to " << filename << "\n\n";
// 2. Load the playlist back from the file
std::vector<Song> loaded_playlist = load_playlist(filename);
std::cout << "Loading playlist from " << filename << "...\n";
// 3. Verify the loaded data
if (loaded_playlist.empty()) {
std::cerr << "Playlist could not be loaded or is empty." << std::endl;
} else {
std::cout << "Successfully loaded " << loaded_playlist.size() << " songs:\n";
for (const auto& song : loaded_playlist) {
std::cout << " - Title: " << song.title << ", Year: " << song.year << std::endl;
}
}
return 0;
}
Note: I've added a try-catch block around std::stoi. This is a robust way to handle cases where the text cannot be converted to an integer, preventing your program from crashing if the file contains malformed data.
Conclusion
Congratulations! You have now mastered a complete and fundamental workflow in C++: taking structured data, serializing it to a file, and parsing it back into memory. This is the foundation for countless real-world applications.
Key Takeaways:
- Parsing is the process of analyzing a string of data to extract meaningful information.
std::stringstreamfrom the<sstream>header is a powerful tool that lets you treat a string like an input stream.- For simple, space-separated data, the extraction operator (
>>) on astringstreamworks well. - For data with specific delimiters (like CSVs), use
std::getline(stream, variable, delimiter)to precisely split strings. - Use functions like
std::stoi(string to int) andstd::stod(string to double) from the<string>header to convert parsed strings into numeric types.
Next Up
You've now built a solid toolkit for procedural programming and data handling in C++. You can create complex data structures, use powerful STL algorithms, and persist your data to files. With this foundation in place, you are ready to explore a new and powerful programming paradigm. In the next module, we will dive into Object-Oriented Programming (OOP) in C++, starting with how to define your own custom types using class.