Skip to content

Differences to Standard Filesystem Implementations

gulrak edited this page Jan 5, 2020 · 16 revisions

Differences Between Actual std::filesystem Implementations and ghc::filesystem

This page tries to collect test cases from the unit tests that currently fail when run against an existing C++17 std::filesystem implementation of more current enough compilers.

[Update: I wrote a more detailed blog post about the differences between various std::filesystem implementations (and as that differences to ghc::filesystem too).]

Node: The test code is based on Catch2 macros.

All tests pass on ghc::filesystem because they are from its test suite. I have by no means the authority nor knowledge of all mails, forums, chats and other channels outside of the standard that have lead the developers to their implementation, and I might have missed an LWG that is relevant here, so I'm not saying mine is the way to do things, and I already changed some behaviour to follow other implementations, as I'm interested in a common sense of what should happen. But some results are still irritating for me, so I keep my implementation the way it is, until I have better arguments otherwise.

I would like to thank Jonathan Wakely at GCCs Bugzilla, as he pointed me into the right directions a few times, was helpful and quick in fixing actual issues I found and reported and Nico Josuttis for his WG21 P1164R0 to clear up the create_directory/create_directories issue!

30.10.8.4.6 path native format observers [fs.path.native.obs]

  CHECK( fs::u8path("\xc3\xa4/\xe2\x82\xac\xf0\x9d\x84\x9e").u16string() == std::u16string(u"\u00E4/\u20AC\U0001D11E") )
due to unexpected exception with messages:
  filesystem error: Cannot convert character sequence: Illegal byte sequence

GCC 8.1, 8.2, 8.3: I had observed this with GCC 8.1/8.2 on Ubuntu 18.04 and macOS, and GCC 8.3.0 on Wandbox. The compiler doesn't recognize the valid Unicode codepoint U+1D11E. (see GCC issue 90281)

30.10.8.6.1 path inserter and extractor [fs.path.io]

For an std::ostringstream filled by:

std::ostringstream os;
os << fs::path("/root/foo bar");

and this failure:

  CHECK( os.str() == "\"\\\\root\\\\foo bar\"" )
with expansion:
  ""/root/foo bar"" == ""\\root\\foo bar""

  CHECK( os.str() == "\"\\\\root\\\\foo\\\"bar\"" )
with expansion:
  ""/root/foo\"bar"" == ""\\root\\foo\"bar""

MSVC++ 14.16: The standard describes the effect of the path inserter as: "Equivalent to: os << quoted(p.string<charT, traits>());" and fs::path::string is one of the native format observers that MSVC returns a backslashed path for. Still the stream inserter outputs the generic format (that Windows API will accept) and this seems inconsistent.

30.10.12.2 directory_entry modifiers [fs.dir.entry.mods]

    std::error_code ec;
    generateFile("foo");
    fs::directory_entry none;
    CHECK_THROWS_AS(none.refresh(), fs::filesystem_error);
    ec.clear();
    CHECK_NOTHROW(none.refresh(ec));
    CHECK(ec);
    CHECK_THROWS_AS(de.assign(""), fs::filesystem_error);
    ec.clear();
    CHECK_NOTHROW(de.assign("", ec));
    CHECK(ec);
    CHECK_THROWS_AS(de.replace_filename("bar"), fs::filesystem_error);
    CHECK_NOTHROW(de.replace_filename("foo"));
    ec.clear();
    CHECK_NOTHROW(de.replace_filename("bar", ec));
    CHECK(ec);

GCC 9.2.0: Using refresh or assign on empty fs::path instances leads to no exception but ec set, when using the error_code variants. Also GCC doesn't throw an exception on replace_filename with a non-existent name, while reporting an error in the error_code variant. That seems inconsistent and ghc::filesystem sees this as an error in all variants.

30.10.15.1 absolute [fs.op.absolute]

    std::error_code ec;
    CHECK(fs::absolute("") == fs::current_path() / "");
    CHECK(fs::absolute("", ec) == fs::current_path() / "");
    CHECK(!ec);

GCC 9.2.0: On Linux/macOS GCC throws an exception with filesystem error: cannot make absolute path: Invalid argument [] or sets the error_code in this case, besides the standard note: "Implementations are strongly encouraged to not query secondary storage, and not consider !exists(p) an error." and the example implementation from the standard: "For POSIX-based operating systems, absolute(p) is simply current_path()/p.".

30.10.15.2 canonical [fs.op.canonical]

  CHECK_THROWS_AS( fs::canonical(""), fs::filesystem_error )
because no exception was thrown where one was expected:

Clang 7, GCC <8.3, MSVC++ 14.16: This seems to be a violation of the standard, as 30.10.15.2 (4) specifies "!exists(p) is an error." and the compiler reports false for exists(""). My guess is, they first call absolute(p) and after that, p is not empty and exists(absolute("")) is normally true as it is the current directory path.

With GCC 8.3.0, the GCC implementation also treats this as an error.

30.10.15.3 copy [fs.op.copy]

  REQUIRE_NOTHROW( fs::copy("dir1", "dir3", fs::copy_options::create_symlinks | fs::copy_options::recursive) )
due to unexpected exception with message:
  filesystem error: in copy: Is a directory [dir1] [dir3]

Clang 7, GCC 8, MSVC++ 14.16: The compilers complain about a copy call with fs::copy_options::recursive combined with fs::copy_options::create_symlinks or fs::copy_options::create_hard_links if the source is a directory. There is nothing in the standard that forbids this combination and it is the only way to deep-copy a tree while only create links for the files. There is LWG #2682 that supports this interpretation, but the issue ignores the usefulness of the combination with recursive and part of the justification for the proposed solution is "we did it so for almost two years". But this makes fs::copy with fs::copy_options::create_symlinks or fs::copy_options::create_hard_links just a more complicated syntax for the fs::create_symlink or fs::create_hardlink operation to create a single link. I'm not sure, if that was the intention of the original writing. As there is another issue related to copy, with a different take on the description, I keep my version the way I read the description, as it is not contradicting the standard and useful. I might need to change that, if the final solution the LWG comes up with will lead to a definitive wording in the standard.

Note: As the current draft leading to C++20 explicitly defines this as an error, I guess, I will adapt to this behaviour, even if I think it is a strange reduced usability of the create_symlink option.

30.10.15.12 equivalent [fs.op.equivalent]

  CHECK_THROWS_AS( fs::equivalent("foo", "foo3"), fs::filesystem_error )
because no exception was thrown where one was expected:
  This test expects LWG #2937 result conformance.

  CHECK( ec )
with expansion:
  system:0
with message:
  This test expects LWG #2937 result conformance.

  CHECK_THROWS_AS( fs::equivalent("foo3", "foo"), fs::filesystem_error )
because no exception was thrown where one was expected:
  This test expects LWG #2937 result conformance.

  CHECK( ec )
with expansion:
  system:0
with message:
  This test expects LWG #2937 result conformance.

GCC 8.x, GCC 9.2: With LWG #2937 was specified, that it is an error if either of the given path objects doesn't resolve to an existing file and ghc::filesystem follows this rule (but there is a define at the top, where this can be disabled). Clang 7 follows 2937