A std::filesystem::path is a class for representing file and directory paths in a portable way across operating systems.
If you’ve programmed in C++ before 2017, you’ve surely faced the nightmare of slashes.
- On Windows, paths use backslashes:
C:\Users\Luis(which in code you must escape asC:\\Users\\Luis). - On Linux and macOS, paths use forward slashes:
/home/luis.
In the past, it was common to fill the code with #ifdef _WIN32 or detect the operating system to choose the separator. The result was harder to maintain and error-prone.
With the arrival of C++17, this is over. We have a class specifically designed to abstract the concept of a “disk location”: std::filesystem::path.
What is fs::path?
std::filesystem::path (or fs::path if we use the common alias) is not simply a std::string. It is an intelligent object that understands the structure of a path.
It knows how to distinguish the filename, extension, parent directory, and drive letter. And most importantly: it handles separators automatically.
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// Define a path generically (using / works on all modern OS)
fs::path ruta = "datos/config/usuario.ini";
std::cout << "Detected path: " << ruta << "\n";
}
Although Windows natively uses \, modern C++ and Windows APIs accept / without issues. fs::path handles translating it to what the operating system needs internally.
Composing Paths with the / Operator
This is, possibly, my favorite feature. You no longer have to concatenate strings worrying about whether you’re missing a trailing slash or not.
The path class overloads the division operator (/) to join folders.
fs::path folder = "project";
fs::path subfolder = "assets";
fs::path file = "texture.png";
// Join the paths adding the correct separator automatically
fs::path fullPath = folder / subfolder / file;
// On Windows it will output: "project\assets\texture.png"
// On Linux it will output: "project/assets/texture.png"
std::cout << fullPath << "\n";
This makes the code cleaner and more readable.
Decomposing Paths
Often we receive a full path (e.g., C:/photos/vacation.jpg) and need to extract just one part. fs::path has methods to dissect the path surgically.
Suppose the path: C:/folder/file.txt
| Method | Description | Example Result |
|---|---|---|
.root_name() | Root/drive name | "C:" (Win) or empty (Linux) |
.root_path() | Root path | "C:/" or "/" |
.parent_path() | The containing folder | "C:/folder" |
.filename() | Full filename | "file.txt" |
.stem() | Name without extension | "file" |
.extension() | Only the extension | ".txt" |
Changing the Extension
Imagine you want to convert a bunch of .png images to .jpg. You need to generate the new filename.
fs::path source = "photo.png";
// Create a copy and replace the extension
fs::path destination = source;
destination.replace_extension(".jpg");
std::cout << "Source: " << source << "\n"; // photo.png
std::cout << "Destination: " << destination << "\n"; // photo.jpg
Conversion Between path and Strings
Although fs::path looks like a string, sometimes we need a real std::string (for example, to pass it to an old library that doesn’t support C++17 or to print it in specific logs).
The path object stores the path in a native system format (which can be wchar_t on Windows).
.string(): Returnsstd::string(Be careful with special characters on Windows)..u8string(): Returns a UTF-8 string; since C++20 its type isstd::u8string..c_str(): Returns a C pointer (const char*orconst wchar_t*).
fs::path filePath = "accents/year.txt";
// To use with fstream (fstream accepts path directly, but good to know)
std::ifstream file(filePath);
// To print or save to a database
std::string pathText = filePath.string();
On Windows, if the path contains Unicode characters (emojis, Chinese characters, etc.), .string() might lose information if the system encoding is not appropriate. In those cases, it’s better to always work with the path object until the very last moment.
Iterating Over a Path
Interestingly, fs::path also acts as a container. If we iterate over it, we get each component of the path.
fs::path path = "/usr/local/bin/gcc";
for (const auto& part : path) {
std::cout << part << "\n";
}
Output:
/
usr
local
bin
gcc