File handling is a special topic in C++17. This is because, the C++17 file-related features were created by including the Boost Filesystem library. In this integration, some of the boost feature were improved, some were cleaned-up and then some were newly added. Therefore, the file topic became a very feature rich and interesting.
To use the new features, a program needs to use following header.
#include <filesystem>
Open an Existing File : File handling
The following example code uses some fundamental file handling functions and classes from C++ 17 filesystem library. example,
- filesystem::path class
- exists( )
- is_regular_file( )
- file_size( )
The last 3 functions are self-explanatory from the name. However, the “path” class is very special. For now, we just assume that it is an element representing the location where the file shall store. Although, there is much more behind this and this topic shall be discussed in Part 2 of FileSystem Series.
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
int main()
{
std::string pathfile = "myfile";
std::filesystem::path p {pathfile};
if (std::filesystem::exists(p))
{
if (std::filesystem::is_regular_file(p))
cout << p << ": "
<< std::filesystem::file_size(p)
<< " bytes\n";
}
else
{
std::cout << "path " << p << " does not exist\n";
}
return 0;
}
Output

Open and Iterate a directory
With minor changes, the above program can work for a directory too. i.e., by using is_directory( ). The function filesystem::is_directory( ) returns true or false. Further, the code below uses a directory_iterator to iterate all the elements present inside this directory. However, the only problem with this iterator is that it cannot recursively iterate the elements inside sub-hierarchies. This limitation shall be removed in by use of a special recursive iterator (after this example).
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
int main()
{
std::string pathfile = "mydir";
filesystem::path p {pathfile};
if (std::filesystem::exists(p))
{
if (std::filesystem::is_directory(p))
{
cout << p << " is a directory:\n";
for (auto& e :
std::filesystem::directory_iterator(p))
{
std::cout << " " << e.path() << '\n';
}
}
}
else
{
std::cout << "path " << p << " does not exist\n";
}
return 0;
}
Output
The code shows that with directory_iterator( ), the program is not printing the file4. Although this is present in the sub-hierarchy.

Recursive Iterator
The above code can use a better iterator that shall iterate the sub-hierarchy in directory tree. When we replace the code in for-loop, it shall provide a better output.
for (auto& e : std::filesystem::recursive_directory_iterator(p))
{
std::cout << " " << e.path() << '\n';
}
However, there is another problem. That is, by default, the recursive iterator shall not follow symbolic links. This means, if an element present in a directory hierarchy is a symlink to another directory, then only link-name shall get printed. The following snapshot shows that the symbolic link name “mydir/mylink” is printed. The program shall not deference this link.

follow_directory_symlink (for dereference)
The recursive_iterator can accept a special option. This shall resolve the symlink problem.
auto options {std::filesystem::directory_options::follow_directory_symlink};
for (auto& e : std::filesystem::recursive_directory_iterator(p, options))
{
std::cout << " " << e.path() << '\n';
}
Complete Example of Directory Iteration
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
std::string pathDir = "mydir";
std::filesystem::path p {pathDir};
if (std::filesystem::exists(p))
{
if (std::filesystem::is_directory(p))
{
auto options
{std::filesystem::directory_options::\
follow_directory_symlink
};
cout << p << " is a directory:\n";
for (auto& e : std::filesystem::recursive_directory_iterator
(p, options))
{
std::cout << " " << e.path() << '\n';
}
}
}
else
{
std::cout << "path " << p << " does not exist\n";
}
return 0;
}
Output

Namespace std::filesystem
All the above features are available under namespace std::filesystem. Therefore, in all the examples above, each command prefixes “std::filesystem”. However, for writing a cleaner code and avoiding the prefix in all items, the following 2 methods are used:
- Explicitly include the namespace with using directive, Example,
using namespace std::filesystem;
2. Define a namespace variable
namespace fs = std::filesystem;
Therefore, use this fs as in following snippet
if (fs::exists(p))
{
if (fs::is_directory(p))
{
}
}
Complete Example Again – cleaner code
The above example is now including namespace with using directive. Therefore, the result is that same code looks much cleaner now.
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
std::string pathDir = "mydir";
path p {pathDir};
if (exists(p))
{
if (is_directory(p))
{
auto options {
directory_options::follow_directory_symlink
};
cout << p << " is a directory:\n";
for (auto& e : recursive_directory_iterator
(p, options))
{
std::cout << " " << e.path() << '\n';
}
}
}
else
{
std::cout << "path " << p << " does not exist\n";
}
return 0;
}
Getting file type with Alternate method : filesystem::status
The above examples had called specific functions to know the type of file.
- is_regular_file(std::filesystem::path&)
- is_directory(std::filesystem::path&)
- is_symlink(std::filesystem::path&)
- is_socket(std::filesystem::path&)
- is_character_file(std::filesystem::path&)
- is_fifo(std::filesystem::path&)
- etc.
However, there is alternative way to achieve the same result. This comes with the function std::status(std::filesystem::path&). The function status( ) returns an object of type “file_status”. This file_status has a type( ) member, which shall return an enum result denoting the file type. Ultimately, a user can use a switch statement to check the type.
enum class file_type {
none = /* unspecified */,
not_found = /* unspecified */,
regular = /* unspecified */,
directory = /* unspecified */,
symlink = /* unspecified */,
block = /* unspecified */,
character = /* unspecified */,
fifo = /* unspecified */,
socket = /* unspecified */,
unknown = /* unspecified */,
/* implementation-defined */
};
Example using file_type enum
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
std::string pathfile = "mydir";
path p {pathfile};
if (exists(p))
{
switch(status(p).type())
{
case file_type::regular:
{
cout << p << " is a file:\n";
break;
}
case file_type::directory:
{
cout << p << " is a directory:\n";
break;
}
default:
{
cout << p << " is a something else\n";
}
}
}
else
{
std::cout << "path " << p << " does not exist\n";
}
return 0;
}
Writing a file in C++
C++ uses fstream classes to write and read from a file. This feature is available from standard C++ and not specific to C++17.
Following simple example demonstrates a file write.
#include <iostream> //main header
#include <fstream>
using namespace std; //for namespace
int main()
{
std::string pathfile = "myfile";
std::ofstream p{pathfile};
if (!p) {
std::cerr << "Error opening for write" << endl;
std::exit(EXIT_FAILURE);
}
p << "The testing starts" << endl;
p << "This is a file created for testing";
p << endl;
p << "The testing ends" << endl;
return 0;
}
Corresponding example for reading the file
#include <iostream> //main header
#include <fstream>
using namespace std; //for namespace
int main()
{
std::string pathfile = "myfile";
std::ifstream p{pathfile};
if (!p) {
std::cerr << "Error opening for reading" << endl;
std::exit(EXIT_FAILURE); // exit program with failure
}
string s;
while(getline(p, s)) // Discards newline char
cout << s << "\n";
return 0;
}
Creating Directory(s) in C++17
There are 2 functions available for creation of directory
- create_directory( )
- create_directories( )
As the name suggests, the former creates a specific directory inside a hierarchy. However, latter creates complete directory tree. Actually, this one is equivalent to “mkdir” with “-p” option.
When the target already exists (directory of same name) the functions just returns “false”. However, in case of any other error, they throw exception.
Simple example of create_directory( )
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
std::string pathdir1 = "mydir";
if(!create_directory(pathdir1))
{
cout << "directory already exists!" << endl;
return 0;
}
cout << pathdir1 << " created successfully!" << endl;
return 0;
}
Simple example of create_directories( )
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
std::string pathdir1 = "mydir1/mydir2/mydir3/mydir4";
if(!create_directories(pathdir1))
{
cout << "directory already exists!" << endl;
return 0;
}
cout << pathdir1 << " created successfully!" << endl;
return 0;
}
Create a link in C++17
There are 2 functions for creating symbolic links.
- create_symlinks( )
- create_directory_symlinks( )
The names are suggesting the background in both cases. A program should always use former for symlinks to files. Secondly, for directories, the later cousin is suitable. Although, create_symlinks( ) may also work with directories in some cases, however, the concept is operating system dependent. Example, some OS do not support symlinks for directories at all whereas some OS need special handling for directory symlinks. Therefore, for ensuring portability, the proper versions should be used.
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
create_symlink("myfile", "link1");
create_directory_symlink("mydir1", "link2");
return 0;
}
Exception for file handling using filesystem library
File operations are generally very complicated. This is because, it depends on many external factors. Hence, It is possible that filesystem library do not handle all error cases with proper return codes. Therefore, the filesystem may throw an exception for such cases.
try
{
}
catch (std::filesystem::filesystem_error& e)
{
}
Possible exception scenarios:
- create_directory can fail. Because, a file or link (not directory) of same name may already exist
- create_symlinks can fail due to wrong inputs
- OS errors
- etc.
Basic example for exception with create_directory( )
In the following example, the directory hierarchy provided as argument actually do not exist. The call shall throw exception.
#include <iostream> //main header
#include <filesystem>//for filesystem
using namespace std; //for namespace
using namespace std::filesystem;
int main()
{
std::string pathdir1 = "mydir/dir1/dir2/dir3";
//dir2 does not exist"
try
{
if(!create_directory(pathdir1))
{
cout << "directory already exists!" << endl;
return 0;
}
cout << pathdir1 << " created successfully!" << endl;
}
catch (filesystem_error& e)
{
std::cerr << "EXCEPTION: " << e.what() << '\n';
std::cerr << " path1: \"" << e.path1().string() << "\"\n";
}
return 0;
}

File Time (file_time_type) Utility
Getting Last Write Time of a File
The filesystem has a utility function last_write_time(path) which returns last modification time or update time of file. The return type is a special type of template class “time_point” in chrono library. It needs to be noted that the return value here is not a system clock time. The file_time_type value needs to convert to system time, i.e. to class type “time_t”. This is because, the chrono utility supports time value of multiple resolutions. The time returned by chrono needs proper conversion to system clock to print the actual time.
namespace std::filesystem {
using file_time_type = chrono::time_point<trivialClock>;
}
The following code demonstrates how to convert time from filesystem to std.
filesystem::file_time_type => std::time_t
#include <iostream> //main header
#include <filesystem>//for filesystem
#include <chrono> //for chrono util
using namespace std;
int main()
{
filesystem::path p = "myfile";
filesystem::file_time_type lwt = last_write_time(p);
std::time_t t = chrono::system_clock::to_time_t(
chrono::system_clock::now()
-
(filesystem::file_time_type::clock::now() - lwt)
);
string ts = ctime(&t);
cout << "last update of " << p << ": " << ts << '\n';
return 0;
}
Output

Main Funda: File handling in C++ 17 provides a lot of features. These have come mainly from boost filesystem library.