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.