Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::filesystem::path::append replace the current path if p starts with root path

Given below code:

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main()
{
    fs::path fsBase = "/base";
    fs::path fsAppend = "/append";
    auto fsResult = fsBase / fsAppend;

    std::cout << "fsResult: " << fsResult << std::endl;
    return 0;
}

Usually, the expected result is /base/append, but it actually gives /append.

The description of fs::path::append does indicate this behavior:

If p.is_absolute() || (p.has_root_name() && p.root_name() != root_name()), then replaces the current path with p as if by operator=(p) and finishes.

However, the behavior of std::experimental::filesystem and boost::filesystem is different, that gives expected /base/append. See examples.

The question is why it behaves like this? Why does it replace the path with append() function?

like image 543
Mine Avatar asked Mar 18 '19 02:03

Mine


People also ask

What is boost :: filesystem :: path?

boost::filesystem::path is the central class in Boost. Filesystem for representing and processing paths. Definitions can be found in the namespace boost::filesystem and in the header file boost/filesystem. hpp . Paths can be built by passing a string to the constructor of boost::filesystem::path (see Example 35.1).

What is filesystem path?

A filesystem is a structure for the computer to store the files and folders that make up the data of the operating system. Inside a filesystem, folders are referred to as directories. Folders that exist inside other folders are called subdirectories.


2 Answers

fsAppend is an absolute path since it starts with / and you're on a system such as POSIX where paths starting with / are absolute.

Appending one absolute path to another absolute path doesn't make any sense (to me throwing an exception would be the most natural result actually). What should the result of C:\foo.txt append C:\bar.txt be?

In experimental::fs the rule was that if the second argument's .native() started with a directory separator then it was treated as a relative path for append purposes, even though it may be an absolute path in other contexts!

The standardized filesystem clearly distinguishes absolute paths from relative paths, trying to avoid this ambiguity that arises on POSIX systems.

The write-up of the change can be found in P0492R2 US77.

Note that you can use += rather than / for concatenation (should do what you expect), or make the second argument relative before using /.

Also see this answer for further comparison between experimental and finalized.

like image 54
M.M Avatar answered Oct 21 '22 16:10

M.M


File names are not (morally) strings: appending a path a and a relative path b structurally answers the question

If a were the current directory, what would the path b mean?

First, if a is the current working directory, this is the relative→absolute function (although filesystem::absolute does a bit more because on Windows D:foo is neither entirely relative nor entirely absolute).

Consider, for example, the behavior of #include"…": if the file name is relative, it is first considered starting from the directory containing the file with the #include, then it is considered starting from each element of the include path (e.g., -I/start/here). Each of those can be phrased as asking the above question:

void handle_include(const std::filesystem::path &reading,
                    const std::filesystem::path &name,
                    const std::vector<std::filesystem::path> &srch) {
  std::ifstream in(reading.parent_path()/name);
  if(!in) {
    for(auto &s : srch) {
      in.open(s/name);
      if(in) break;
    }
    if(!in) throw …;
  }
  // use in
}

What should happen if name is absolute (e.g., #include"/usr/include/sys/stat.h")? The only correct answer is to use name without considering reading or s. (Here, that would inefficiently consider the same file several times, but that’s a matter of efficiency, not correctness, and affects only the error case.) Note also the related identity that a/b.lexically_proximate(a)==b; lexically_proximate can return an absolute path (when the two paths have different root names), whereas lexically_relative can fail and lose information.

This approach also avoids the gratuitously useless answer that blind concatenation gives on Windows: C:\foo\D:\bar isn’t even a valid file name, let alone one that anyone could have meant to obtain by combining its pieces. Certainly raising an exception would avoid that as well, but at the cost of preventing the above reasonable use case. There is even the case of path("c:\\foo\\bar").append("\\baz\\quux"), which keeps part of each and produces path("c:\\baz\\quux"), which is again the correct answer to the question above.

Given that no one should be writing things like

[project]
  headers=/include
  manual=/doc

there’s no reason for the right-hand operand to be absolute when this interpretation is incorrect. (Obviously, if it is, one can write base/cfg.relative_path(); this is the answer to the follow-on question in a comment.)

The inspiration for the behavior was Python’s os.path.join, which does exactly this with each argument in turn.

like image 37
Davis Herring Avatar answered Oct 21 '22 16:10

Davis Herring