- 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();
p.filename();
p.stem();
p.extension();
p.parent_path();
p.replace_extension(".bak");
fs::path dir = "/var/log";
fs::path file = dir / "app.log";
file /= "rotated";
- 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);
fs::is_regular_file(p);
fs::is_directory(p);
fs::is_symlink(p);
fs::is_empty(p);
fs::file_size(p);
auto t = fs::last_write_time(p);
auto s = fs::status(p);
auto perms = s.permissions();
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)) { }
3. Iterating Directories
namespace fs = std::filesystem;
for (const auto& entry : fs::directory_iterator{"/var/log"}) {
std::cout << entry.path() << "\n";
}
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;
fs::create_directory("/tmp/foo");
fs::create_directories("/tmp/foo/bar/baz");
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");
fs::remove("file.txt");
fs::remove_all("/tmp/foo");
fs::create_symlink("/var/log/app.log", "current.log");
fs::read_symlink("current.log");
fs::permissions("script.sh", fs::perms::owner_exec, fs::perm_options::add);
5. Error Handling
- Every filesystem function has 2 overloads:
- Throws
std::filesystem::filesystem_error on failure.
- Takes
std::error_code& — reports failure through it, no throw.
namespace fs = std::filesystem;
try {
auto sz = fs::file_size("missing.txt");
} catch (const fs::filesystem_error& e) {
std::cerr << e.what() << " path: " << e.path1() << "\n";
}
std::error_code ec;
auto sz = fs::file_size("missing.txt", ec);
if (ec) {
std::cerr << "error: " << ec.message() << "\n";
}
- 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).
- Separators — use
/ in literals; path translates as needed. Don't hardcode backslashes on Windows.
- Case sensitivity — POSIX = case-sensitive; Windows + macOS HFS+/APFS = case-insensitive, case-preserving. Don't rely on case-only differences.
- Permissions — POSIX
perms map cleanly; Windows ACLs don't. permissions() API is approximate on Windows.
- Symbolic links — Windows requires admin/dev mode; may fail otherwise.