directory_iterator
and recursive_directory_iterator
.A common requirement we’ll have is to understand what files and directories exist in a specific location on the file system.
We can accomplish this using iterators that are provided within the std::filesystem
namespace, which we’ll alias to fs
in this lesson.
We can navigate through a directory using a fs::directory_iterator
pair. By passing a fs::path
to this type, we can define the directory we want to iterate through.
The default constructor for fs::directory_iterator
returns a sentinel, which we can use to indicate when we’ve iterated through all the files and directories in the path. We cover iterators and sentinels in more detail in these lessons:
To use these iterators manually, we can increment our starting iterator until it becomes equal to the default-constructed sentinel:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::directory_iterator Start{R"(c:\test)"};
fs::directory_iterator End{};
for (auto Iter{Start}; Iter != End; ++Iter) {
std::cout << Iter->path().string() << '\n';
}
}
c:\test\file.txt
c:\test\hello.backup
c:\test\hello.txt
c:\test\subdirectory
Instead of working with iterators directly, we can use them to create a subrange. We can then use this subrange to apply range-based techniques. Below, we iterate through a directory using a range-based for loop:
#include <filesystem>
#include <iostream>
#include <ranges>
namespace fs = std::filesystem;
int main() {
using std::ranges::subrange;
subrange DirectoryEntries{
fs::directory_iterator{R"(c:\test)"}};
for (const auto& Entry : DirectoryEntries) {
std::cout << Entry.path().string() << '\n';
}
}
c:\test\file.txt
c:\test\hello.backup
c:\test\hello.txt
c:\test\subdirectory
Below, we use a range-based algorithm (std::ranges::for_each
) to output some additional information about the contents of our directory:
#include <filesystem>
#include <iostream>
#include <ranges>
namespace fs = std::filesystem;
void Log(const fs::directory_entry& Entry) {
std::cout << Entry.path().string();
if (Entry.is_directory()) {
std::cout << " (Directory)\n";
} else if (Entry.is_regular_file()) {
std::cout << " ("
<< Entry.file_size() << " Bytes)\n";
}
}
int main() {
using std::ranges::subrange;
std::ranges::for_each(
fs::directory_iterator{R"(c:\test)"}, Log);
}
c:\test\file.txt (0 Bytes)
c:\test\hello.backup (24 Bytes)
c:\test\hello.txt (24 Bytes)
c:\test\subdirectory (Directory)
By default, the fs::directory_iterator
only iterates through the first level of entries within our directory. If an entry is a subdirectory, we will not navigate into it.
We could build this logic ourselves, by implementing recursive behavior when fs::is_directory
returns true. However, the standard library has built this for us, in the form of the fs::recursive_directory_iterator
The recursive directory iterator works the same way as the fs::directory_iterator
, except it will navigate into any subdirectories it finds along the way.
The following code is almost identical to the previous example. Only a single line (which we’ve highlighted) has been changed, switching our iterator type:
#include <filesystem>
#include <iostream>
#include <ranges>
namespace fs = std::filesystem;
int main() {
using std::ranges::subrange;
subrange DirectoryEntries{
fs::recursive_directory_iterator{
R"(c:\test)"}};
for (const auto& Entry : DirectoryEntries) {
std::cout << Entry.path().string();
if (Entry.is_directory()) {
std::cout << " (Directory)\n";
} else if (Entry.is_regular_file()) {
std::cout << " (" << Entry.file_size()
<< " Bytes)\n";
}
}
}
We can now navigate into subdirectories:
c:\test\file.txt (0 Bytes)
c:\test\hello.backup (24 Bytes)
c:\test\hello.txt (24 Bytes)
c:\test\subdirectory (Directory)
c:\test\subdirectory\deep.txt (10 Bytes)
The recursive iterator has an additional method, called depth()
. This will return the depth of the recursion, ie, how deeply nested the current directory entry is, relative to our starting point:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::recursive_directory_iterator Start{
R"(c:\test)"};
fs::recursive_directory_iterator End{};
for (auto Iter{Start}; Iter != End; ++Iter) {
std::cout << "Depth " << Iter.depth()
<< " - " << Iter->path().string()
<< '\n';
}
}
Depth 0 - c:\test\file.txt
Depth 0 - c:\test\hello.backup
Depth 0 - c:\test\hello.txt
Depth 0 - c:\test\subdirectory
Depth 1 - c:\test\subdirectory\deep.txt
Up next, we’ll start working with file streams. These are the main ways we create, read, and update files within our file systems.
In this lesson, we've explored the essentials of directory iteration, demonstrating how to navigate and manage file systems using fs::directory_iterator
and fs::recursive_directory_iterator
.
fs::directory_iterator
: Learned how to utilize fs::directory_iterator
to traverse a directory, handling each file and subdirectory within a given path using a straightforward for-loop.fs::recursive_directory_iterator
: Explored the use of fs::recursive_directory_iterator
for deep navigation into directories, automatically iterating through all subdirectories.depth()
method of the recursive directory iterator to ascertain the level of recursion and understand the hierarchy of files and directories.An introduction to iterating through the file system, using directory iterators and recursive directory iterators
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.