3

The function boost::filesystem::canonical() (doc of 1.66, doc of current release) offers two arguments (ignoring the error code overload) base. The first one is the path to canonicalize, the second argument is the base path used to make the first path absolute if it is relative. By default current_path() is used for this argument.

Boost 1.60 introduces some new functions, among them boost::filesystem::weakly_canonical() (doc of 1.66, doc of current release). This function is missing this second argument. The same is true for the standardized (C++17) variants std::filesystem::canonical() and std::filesystem::weakly_canonical() (see cppreference).

I want to exchange canonical() with weakly_canonical(), but I used the second argument. That is how I realized that this argument was removed. Now I'm wondering why it was removed and how I can make the path absolute myself.

I found a defect report which hinted to this resolution for C++17, but frankly I don't really get the rationale. I'd be happy about an explanation or maybe better an example where the overload with base would be overspecified.

And of course I'm wondering how I then should convert a relative path into an absolute path using a base directory which is not the current directory. Should I simply use base / p as hinted on cppreference for std::filesystem::absolute() because I know that this is the correct form on my target system (Windows with Visual C++)?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Brandlingo
  • 2,817
  • 1
  • 22
  • 34
  • Somewhat related: [How similar are Boost filesystem and the standard C++ filesystem libraries?](https://stackoverflow.com/questions/40899267/how-similar-are-boost-filesystem-and-the-standard-c-filesystem-libraries) – Brandlingo Apr 05 '18 at 19:34

1 Answers1

1

OK, here are the circumstances that you might have when you have a relative path and want to call such a function:

  1. You know the path is relative to the current_path.
  2. You know the path can be made absolute by calling absolute. Note that thanks to filesystems like Windows, this is not the same thing as saying that it is relative to the current_path.
  3. You know the path is relative to some known absolute_path which is not the current path.
  4. You don't know if the path is relative or not.

In case #4, your first step needs to be to find out whether it is relative and if so what it is relative to. Once that is done, you're back to cases 1-3.

In each of the cases 1-3, you have a direct way to compute the absolute path. In case 1, you use current_path() / rel. In case 2, you use absolute(rel). In case 3, you use absolute_path / rel. (note: This is not only "the correct form on my target system (Windows with Visual C++)", this is the correct form period.)

In the original version of canonical/weakly_canonical, the functions only handled cases 1 and 3. Case 2 was impossible to handle within the function. By making the functions lower-level, by making them use absolute for relative paths rather than taking a base path that defaults to current_path(), this allows the functions to handle case 2 as well as the other cases.

They could have changed it so that there would be overloads that don't take a path (rather than a default current_path()) which would use absolute. But really, what's the difference between canonical(rel, absolute_path) and canonical(absolute_path / rel)? Indeed, the latter makes it much clearer what you're doing, since it puts the absolute path on the left, which is where it goes.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Thanks for your answer. I guess my main problem is that I don't see the difference between 1 and 2. Is there any example you could give? Do you refer to NTFS with "filesystem Windows"? And if `abs_path / rel_path` is _the_ correct form, why then the non-POSIX hint in cppreference? – Brandlingo Jan 13 '18 at 17:27
  • The note in cppreference is for a *specific case*. It's saying that `absolute` behaves that way on POSIX filesystems. But on Windows (for example), it doesn't. That's the point. As for the difference between 1 and 2, that's a matter of the fact that there isn't always a single "current path" on a filesystem. `d:myfile.txt` is a relative path. Its proper absolute version uses the current path that the `D` drive is currently using. That's not necessarily the same as what you get from `current_path`. In Windows, each driver has a separate current path. – Nicol Bolas Jan 13 '18 at 17:43
  • Is the problem relative paths with root names such as `"D:file.txt`? When `current_path() == "C:\\local"` then `absolute("D:file.txt")` might return `"D:\\local\\file.txt"` whereas `path("C:\\local") / path("D:file.txt")` returns `"C:\\local\\D:file.txt"`? (at least that's the result with boost 1.56 on Windows, with [Microsoft's std::tr2::sys implemenation](https://msdn.microsoft.com/en-us/library/hh874769.aspx) and with Microsoft's implementation of `std::experimental::filesystem`) – Brandlingo Jan 13 '18 at 18:01
  • OK, so that **is** the problem. (didn't see your comment in time) Never thought of such kind of relative paths. Thank you very much. – Brandlingo Jan 13 '18 at 18:05
  • But given `abs == path("C:\\local") != current_path()` and `rel == path("D:file.txt")` then there is no function that would return `"D:\\local\\file.txt"`, I could only use `abs / rel` to retrieve the illegal path `"C:\\local\\D:file.txt"`. I guess we'll have to live with that. – Brandlingo Jan 13 '18 at 18:20
  • @MatthäusBrandl: "*then there is no function that would return "D:\\local\\file.txt"*" Why would there be? The current path on `D:` is probably not `D:/local`. So why would a function ever return that? You need to ask yourself the question, "what does `D:file.txt` mean to the person who gave it to you?" It almost certainly *does not* refer to a file in `C:`, since they went through the trouble of explicitly specifying `D:`. – Nicol Bolas Jan 13 '18 at 18:23
  • Of course, you are absolutely right. So the best idea might be to test for `rel.root_name().empty()` and do something meaningful in case it is not. – Brandlingo Jan 13 '18 at 18:41
  • @MatthäusBrandl: Or, you can just use `absolute`. That's *why it is there*. – Nicol Bolas Jan 13 '18 at 18:47
  • @MatthäusBrandl `path("C:\\local") / path("D:file.txt")` is `path("D:file.txt")`. – T.C. Jan 13 '18 at 21:53
  • @NicolBolas I can't because the base directory usually is not the current directory (of that drive). base is the parent directory of one file which is referencing other files, possibly with relative paths. I need to make these paths absolute. That's the whole reason for my question for my question after all. – Brandlingo Jan 14 '18 at 19:40
  • @T.C. Not true, at least not for the implentations I tested. I did not simply state the result of [comment #3](#comment83467854_48230478). The result of `path("C:\\local") / path("D:file.txt")` really is `path("C:\\local\\D:file.txt")`. This is also true for libstdc++ on POSIX systems, see [this example](http://coliru.stacked-crooked.com/a/5aae257eeac599c8) on coliru. (also showing a bug in the libstdc++ implementation) – Brandlingo Jan 14 '18 at 19:54
  • @MatthäusBrandl I'm telling you what C++17 says `operator /` produces. – T.C. Jan 14 '18 at 20:17
  • @T.C. I see, (given `p = path("D:file.txt")`) although `!p.is_absolute()` still `p.has_root_name()` (at least on windows, on a POSIX system this should return false, and it is on coliru) and root names are different. Sorry for my reaction, using a small comment such as "standard mandates that" would have helped me. I now realize that the reason is that I only tested implementations of `std::experimental::filesystem` for which (at least according to cppreference) `append()` was specified differently. Thanks for clarifying this. – Brandlingo Jan 14 '18 at 20:50