std::filesystem::path
type.In the previous lessons, we represented our file paths as simple strings, but the standard library provides a dedicated class for this: std::filesystem::path
.
This type provides additional utility specific to working with the file system. We’ll alias it to fs::path
in this lesson.
fs::path
ObjectsWe can create fs::path
objects using simple strings:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path Location{R"(c:\test)"};
}
We can get the string representation of a path using the string()
method, which is useful when we want to display it:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path Location{R"(c:\test)"};
std::cout << Location.string();
}
c:\test
fs::path
Objects with Directory EntriesThe fs::directory_entry
constructor we’ve been using in the previous lesson accepts an fs::path
 argument:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path Location{R"(c:\test)"};
fs::directory_entry File{Location};
}
Since fs::path
can be created from a string, our fs::path
objects were being created implicitly:
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// Implicitly converting raw string to fs::path
fs::directory_entry File{R"(c:\test)"};
}
We can get the fs::path
associated with a fs::directory_entry
using the path()
 method:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::directory_entry Entry{R"(c:\test)"};
std::cout << Entry.path().string();
}
c:\test
fs::path
ComponentsA variety of methods give us access to specific parts of the path. These also return fs::path
objects, so in the following example we use the string()
method to display them:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path Location{R"(c:\test\hello.txt)"};
std::cout << "File Name: "
<< Location.filename().string();
std::cout << "\nFile Stem: "
<< Location.stem().string();
std::cout << "\nFile Extension: "
<< Location.extension().string();
std::cout << "\nParent Path: "
<< Location.parent_path().string();
std::cout << "\nRoot Path: "
<< Location.root_name().string();
}
File Name: hello.txt
File Stem: hello
File Extension: .txt
Parent Path: c:\test
Root Path: c:
Equivalent boolean methods return true
or false
based on the existence of any of these path components.
We can access these by prepending has_
to the method names. For example, has_filename()
and has_extension()
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path Directory{R"(c:\test\)"};
if (!Directory.has_filename()) {
std::cout << Directory.string()
<< " has no file name\n";
}
fs::path File{R"(c:\hi.txt)"};
if (File.has_extension()) {
std::cout << File.string()
<< " has a file extension";
}
}
c:\test\ has no file name
c:\hi.txt has a file extension
Note that the result of these functions is based only on the format of the provided string. For example, the has_filename()
method returns true
if it appears that the provided string has a filename.
To access the file system and check whether there really is a file at that path, we need to create a fs::directory_entry
, not just a fs::path
. We can then call a method like is_regular_file()
, as we covered in the previous lesson.
fs::path
file namesWhen working with paths, a common requirement is to manipulate the file name. We have some methods to help us there:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path File{R"(c:\test\hello.txt)"};
std::cout << File.string() << '\n';
File.replace_filename("world.txt");
std::cout << File.string() << '\n';
File.replace_extension("doc");
std::cout << File.string() << '\n';
File.remove_filename();
std::cout << File.string();
}
c:\test\hello.txt
c:\test\world.txt
c:\test\world.doc
c:\test\
Use cases for these methods typically come up when we're creating reusable functions.
For example, the following function creates a file, but the location of the file it creates is derived from an argument. In this case, it will create the file c:\test\hello.backup
:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void CreateBackup(const fs::path& Path) {
fs::path Backup{Path};
Backup.replace_extension("backup");
fs::copy_file(Path, Backup);
}
int main() {
CreateBackup(R"(c:\test\hello.txt)");
}
fs::path
The fs::path
type also overrides the /=
operator, which allows us to create paths to subdirectories or files. This is done by automatically appending separators that are appropriate to the underlying operating system:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path File{R"(c:\)"};
std::cout << File.string() << '\n';
File /= "test";
std::cout << File.string() << '\n';
File /= "hello.txt";
std::cout << File.string() << '\n';
}
c:\
c:\test
c:\test\hello.txt
This operator, and most of the fs::path
methods, returns a reference to the original object. This allows them to be chained:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path File{R"(c:\test\hello.txt)"};
std::cout << File.string() << '\n';
File.remove_filename() /= "subdirectory";
std::cout << File.string() << '\n';
(File /= "nested") /= "directory";
std::cout << File.string();
}
c:\test\hello.txt
c:\test\subdirectory
c:\test\subdirectory\nested\directory
All the paths we’ve shown so far have been absolute paths. If we wanted to access files in an exact location, we should use absolute paths.
However, if we want to access files in a location relative to where our program is installed, we don’t necessarily know the exact location in advance. For this, we use relative paths.
Relative paths are based on another directory, often referred to as the current path or current working directory.
We can check if a path is relative or absolute using the is_relative()
and is_absolute()
 methods:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path A{R"(c:\test\hello.txt)"};
if (A.is_absolute()) {
std::cout << "A is Absolute";
}
fs::path B{R"(hello.txt)"};
if (B.is_relative()) {
std::cout << "B is Relative";
}
}
A is Absolute
B is Relative
We can retrieve the current path that relative paths are based on using the current_path()
method. Its default value depends on our settings, but we can pass a new path to that function to set a new current path for our relative paths:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::directory_entry Entry{R"(hello.txt)"};
std::cout
<< "The default working directory is:\n"
<< fs::current_path().string();
if (!Entry.exists()) {
std::cout << "\nThe file was not found\n\n";
}
// Setting the current path to a new lcoation
fs::current_path(R"(c:\test)");
std::cout << "The current working directory "
"was changed to:\n"
<< fs::current_path().string();
if (Entry.exists()) {
std::cout << "\nThe file was found!";
}
}
The working directory is:
C:\Users\ryan\repos\cpp
The file was not found
The working directory was changed to:
c:\test
The file was found!
In this lesson, we explored the versatile capabilities of std::filesystem::path
, demonstrating how to create, manipulate, and use file paths effectively. We covered various operations from basic path creation to advanced manipulations.
fs::path
objects for representing and manipulating file paths./=
operator, with examples demonstrating path chaining.fs::current_path()
.A guide to effectively working with file system paths, using the path
type within the standard library's filesystem
module.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.