C++ Filesystem


  • Description: A note on std::filesystem (C++17) — path, queries, traversal, file operations, error handling, and cross-platform considerations
  • My Notion Note ID: K2A-B1-23
  • Created: 2018-07-10
  • Updated: 2026-02-28
  • License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io

Table of Contents


1. std::filesystem::path

  • std::filesystem::path (C++17, <filesystem>) — filesystem location type. Handles encoding, separators, decomposition uniformly across platforms.
#include <filesystem>
namespace fs = std::filesystem;

fs::path p = "/var/log/app.log";

p.string();              // "/var/log/app.log"
p.filename();            // "app.log"
p.stem();                // "app"
p.extension();           // ".log"
p.parent_path();         // "/var/log"

p.replace_extension(".bak");   // "/var/log/app.bak"

// Composition with /=  and  /
fs::path dir = "/var/log";
fs::path file = dir / "app.log";        // "/var/log/app.log"
file /= "rotated";                       // "/var/log/app.log/rotated"
                                         // /= treats both sides as directory parts
  • Path arithmetic (/, /=) handles platform separator automatically. Don't concat strings manually.

2. Queries: Existence, Type, Size, Time

namespace fs = std::filesystem;

fs::path p = "/etc/hosts";

fs::exists(p);                  // true
fs::is_regular_file(p);         // true
fs::is_directory(p);            // false
fs::is_symlink(p);              // false
fs::is_empty(p);                // false (file is non-empty)

fs::file_size(p);               // size in bytes (regular files only)

auto t = fs::last_write_time(p);          // file_clock time_point
auto s = fs::status(p);                    // permissions + type (cached)
auto perms = s.permissions();              // permission bits
  • status(p) = cheapest single-stat. Boolean helpers (is_directory(p)) re-stat each call. In tight loops → cache:
auto s = fs::status(p);
if (fs::is_regular_file(s)) { /* ... */ }   // reuses the cached status

3. Iterating Directories

namespace fs = std::filesystem;

// Non-recursive
for (const auto& entry : fs::directory_iterator{"/var/log"}) {
    std::cout << entry.path() << "\n";
}

// Recursive
for (const auto& entry : fs::recursive_directory_iterator{"/var/log"}) {
    if (entry.is_regular_file()) {
        std::cout << entry.path() << " (" << entry.file_size() << ")\n";
    }
}
  • Directory iterators yield directory_entrys — cache file status for cheap repeated queries.

4. File Operations

namespace fs = std::filesystem;

// Create
fs::create_directory("/tmp/foo");
fs::create_directories("/tmp/foo/bar/baz");     // mkdir -p

// Copy / move
fs::copy("a.txt", "b.txt");
fs::copy("src/", "dst/", fs::copy_options::recursive | fs::copy_options::overwrite_existing);
fs::rename("old.txt", "new.txt");

// Remove
fs::remove("file.txt");                          // single file
fs::remove_all("/tmp/foo");                      // recursive (returns count)

// Symlinks
fs::create_symlink("/var/log/app.log", "current.log");
fs::read_symlink("current.log");                 // "/var/log/app.log"

// Permissions
fs::permissions("script.sh", fs::perms::owner_exec, fs::perm_options::add);

5. Error Handling

  • Every filesystem function has 2 overloads:
  1. Throws std::filesystem::filesystem_error on failure.
  2. Takes std::error_code& — reports failure through it, no throw.
namespace fs = std::filesystem;

// Throwing version
try {
    auto sz = fs::file_size("missing.txt");
} catch (const fs::filesystem_error& e) {
    std::cerr << e.what() << " path: " << e.path1() << "\n";
}

// Non-throwing version
std::error_code ec;
auto sz = fs::file_size("missing.txt", ec);
if (ec) {
    std::cerr << "error: " << ec.message() << "\n";
}

6. Cross-Platform Considerations

  1. Encoding — POSIX: char (typically UTF-8). Windows: wchar_t (UTF-16). path::native() = platform-native; path::string() converts (lossy on Windows for non-Latin filenames — use path::u8string() for UTF-8).
  2. Separators — use / in literals; path translates as needed. Don't hardcode backslashes on Windows.
  3. Case sensitivity — POSIX = case-sensitive; Windows + macOS HFS+/APFS = case-insensitive, case-preserving. Don't rely on case-only differences.
  4. Permissions — POSIX perms map cleanly; Windows ACLs don't. permissions() API is approximate on Windows.
  5. Symbolic links — Windows requires admin/dev mode; may fail otherwise.