C++ 17 – Filesystem Library

When you want to do filesystem-level operations, a C++ developer used to rely on platform-specific APIs to do the operations. For example, if we’re going to parse a directory, check if a file exists, or check the size of the file, we used to link with POSIX APIs in Unix systems and Windows API on Windows systems.

C++ 17 introduced the filesystem library that will take care of these operations. It provided platform-independent APIs in C++ itself. All we need to do is “include filesystem” and use functions from the namespace “std::filesystem”.

The Filesystem Library provided API functions for standard file and directory tasks, such as iterating through directories, creating or removing files, and querying file attributes. The library helps developers abstract away platform-specific details and write portable code.

Here is a simple example C++ program using the most important filesystem library methods.

$ cat fs.cpp
#include <iostream>
#include <filesystem>

int main() {
    // Note that it is sub namespace std::filesystem
    namespace fs = std::filesystem;

    // This is the path to examine
    fs::path explore_path = "./dir_to_examine";

    // Check the path exists and is a directory
    if (fs::exists(explore_path) && fs::is_directory(explore_path)) {
        uintmax_t total_size = 0;

        // Parse through the directory
        for (const auto& entry : fs::directory_iterator(explore_path)) {
            auto status = fs::status(entry);

            // Check if the path is directory or file
            std::cout << (fs::is_directory(status) ? "[Directory]" : "[File]     ") << " " << entry.path() << '\n';

            // Add file size to total_size if it is file
            if (fs::is_regular_file(status)) {
                total_size += fs::file_size(entry);
            }
        }

        std::cout << "Total size of regular files is: " << total_size << " bytes\n";
    } else {
        std::cerr << "Invalid directory: " << explore_path << '\n';
    }

    return 0;
}

The code is available for download at https://github.com/codeversionmaster/cplusplus/blob/cplusplus17/fs.cpp.

First, let us try the C++ 14 compiler to see if it existed before C++ 17. It will not compile and says the filesystem is unknown to it.

$ g++ -std=c++14 fs.cpp
fs.cpp: In function ‘int main()’:
fs.cpp:6:25: error: ‘filesystem’ is not a namespace-name; did you mean ‘system’?
    6 |     namespace fs = std::filesystem;
      |                         ^~~~~~~~~~
      |                         system
...
...

Compile using C++ 17 compiler, and it compiles well.

$ g++ -std=c++17 fs.cpp -o fs

We created a directory “dir_to_examine” in the current directory and given the same inside the program. “dir_to_examine” is below. One subdirectory and one regular file are present.

$ ls dir_to_examine/
fileone  subdir

The program would parse through this directory and list down if each directory is a subdirectory or file. It also would calculate the total size of regular files in this directory.

Note: It will not parse sub-directories recursively. It would only parse the main directory that was given for examination. We want to keep the program simple and focus more on learning the usage of the filesystem library APIs.

Let us run the program.

$ ./fs
[File]      "./dir_to_examine/fileone"
[Directory] "./dir_to_examine/subdir"
Total size of regular files is: 34 bytes

Explanation of the program

Now let us see each portion of the code and understand what it does.

Below is the initialization of the header and having a namespace handy for the library functions.

#include <filesystem>
namespace fs = std::filesystem;

We are using a fs::path variable to store a filesystem path.

 fs::path explore_path = "./dir_to_examine";

fs::exists checks if the path provided exists. fs::is_directory checks if the path is a directory.

 if (fs::exists(explore_path) && fs::is_directory(explore_path))

Using fs::directory_iterator, we can parse through each entry in the path. Each entry’s properties would be available in the variable entry in each iteration of the loop.

const auto& entry : fs::directory_iterator(explore_path))

fs::status gives the status of the file as a return value. The status is used in other functions, such as fs::is_directory, to know if it is a directory or file. entry.path would give the path of the sub-directory or file.

auto status = fs::status(entry);
 std::cout << (fs::is_directory(status) ? "[Directory]" : "[File]     ") << " " << entry.path() << '\n';

fs::is_regular_file gives true if it is a regular file. fs::file_size function acting on the entry variable would provide the file size in bytes.

            if (fs::is_regular_file(status)) {
                total_size += fs::file_size(entry);
            }

As you can see, if these library API functions did not exist, we had to learn and use the platform-based functions for each operation and use them. The filesystem library removes all the burden on the C++ developer from C++ 17 version onwards.