Directory Iterators

An introduction to iterating through the file system, using directory_iterator and recursive_directory_iterator.

Ryan McCombe
Updated

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:

namespace fs = std::filesystem;

Using Directory Iterators

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:

Iterator and Range-Based Algorithms

An introduction to iterator and range-based algorithms, using examples from the standard library

Defining Ranges using Sentinels

An alternative way of defining ranges, and why we sometimes need to use them

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

Iterating Directories using Ranges

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

Recursive Directory Iteration

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.

Summary

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.

Key Takeaways

  • Utilizing 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.
  • Creating Subranges with Iterators: Gained skills in generating subranges from directory iterators, unlocking the ability to use range-based techniques and algorithms in our subdirectory.
  • Navigating Deeply with fs::recursive_directory_iterator: Explored the use of fs::recursive_directory_iterator for deep navigation into directories, automatically iterating through all subdirectories.
  • Employing the Depth Method: Acquired knowledge on using the depth() method of the recursive directory iterator to ascertain the level of recursion and understand the hierarchy of files and directories.
Next Lesson
Lesson 124 of 128

File Streams

A detailed guide to reading and writing files in C++ using the standard library's fstream type

Questions & Answers

Answers are generated by AI models and may not have been reviewed. Be mindful when running any code on your device.

Network Paths with Directory Iterators
Can directory_iterator be used with network paths?
Filter Directory Entries
How do I filter the directory entries to only show files?
Handling Missing Directories with directory_iterator
What happens if the directory path does not exist when creating a directory_iterator?
Skip Files or Directories using directory_iterator
How can I skip certain files or directories during iteration?
Sort Directory Entries
Is it possible to sort the directory entries while iterating?
Handle Symbolic Links During Directory Iteration
How do I handle symbolic links when using directory_iterator?
Get File Attributes During Directory Iteration
Can I use directory_iterator to get file attributes?
Stop Directory Iteration Early
How can I stop the iteration prematurely when using directory_iterator?
Count Files in Directory
How can I count the number of files in a directory?
Use Directory Iterator with Multithreading
How can I combine directory_iterator with multithreading?
Use Relative Paths with Directory Iterator
Can directory_iterator be used with relative paths?
Or Ask your Own Question
Get an immediate answer to your specific question using our AI assistant