Writing std::filesystem
Let’s write our own implementation of std::filesystem. Here’s what we’re going to work from.
- C++17 draft standard - see 27.10 File systems
- File System Technical Specification - essentially what’s in the standard
- Boost Filesystem Library - the TS came from Boost, after all
- filesystem (Visual Studio 2015)
- libcstdc++ Filesystem
- Filesystem library (cppreference.com)
This shipped as std::experimental::filesystem
in Visual Studio 2015 (don’t think VS2017 promoted it to
std::filesystem).
It shipped as std::experimental::filesystem
in GCC 5.3 in libstdc++.
While it’s implemented in libc++, also as std::experimental::filesystem
,
it’s not shipping with a Clang version yet (you need to build your own Clang/libc++ to enable it).
So, let’s just write our own so we can start using it everywhere. Since we’re starting from the fact of
the C++17 standard now including it, we will just create std::filesystem
. This makes our life a little
easier at the moment, since no vendor has yet promoted it out of experimental.
<filesystem&rt;
This is somewhat defined for us in the standard. This goes in the namespace std::filesystem
. We
wrap it like this:
Note the inline namespace v1
. See What are inline namespaces for? for an explanation about using inline namespaces to help
in API versioning.
The classes in our namespace:
There are also a host of non-member functions to implement. We’re going to group these by functionality, rather than address them all at once.
Let’s cover trivial stuff first.
file_time_type
The standard says that trivial-clock
is an implementation-defined type that satisfies
the C++ concept TrivialClock
(all this means is that this is a clock that would make the
chrono
library happy) and is sufficient to represent the resolution and range of the file time
values offered by the filesystem. What the fuck, standard committee! Right off the bat,
we have something that could differ between filesystems and thus we can’t really pass these
values between file systems.
See http://en.cppreference.com/w/cpp/concept/TrivialClock
for something readable about TrivialClock
.
We could take the easy route. Since all the chrono
clocks meet the TrivialClock
requirements,
we could just do this:
Let’s call that the v0 implementation.
We’re going to put a line in the sand. Our clock is 64-bit nanoseconds with a zero at 2000/01/01 00:00:00 UTC.
This has the happy property that it’s a superset of Windows FILETIME
and most Linux and Unix filesystems
that I’ve ever heard of, a superset that is losslessly convertible.
file_type
This one is easy, it’s a mandated enum class. The values aren’t mandated, but we’ll put the exceptional case as -1 and then just number others starting with 0.
Note that we end our last enum item with a comma; this is a new best practice in my opinion, because it means that adding new items can’t go awry or cause extra lines to be edited.
copy_options
This one is also easy, another mandated enum class without mandated values. This is a bitmask enum, which means values will be combined together into a single field. So we just start with zero and roll up by powers of 2 to create distinct bitmaps.
Along with the enum class, we need some operators so that we can combine copy_options together; in the spec, it’s stated as a requirement that the concept BitmaskType is supported: see C++ concepts: BitmaskType.
perms
This one is also easy, another mandated enum class but with mandated values and the operators to support the BitmaskType concept.
directory_options
This one is also easy, another mandated enum class without mandated values. This is a bitmask enum, which means values will be combined together into a single field. So we just start with zero and roll up by powers of 2 to create distinct bitmaps, and add the operators as per BitmaskType.
space_info
This is defined in the standard, so hurray, but then the commentary about the space()
function itself
has a bunch of as-if POSIX, instead of just stating the obvious.
Specifically, capacity
is the total size of the filesystem in bytes; free
is the size of the
unallocated part of the filesystem in bytes; and available
is the number of bytes available to
a non-super user. As such, available
can be less than free
.