0
fs::path p1 = "/q/b";
fs::path p2 = "/";
std::cout << p1 / p2 << std::endl;

will output /.

It is totally unexpected from so many perspectives:

  • Purely logical: appending (that is how the operation is called, right?) something to something cannot overwrite the former.
  • Current Unix path behaviour: for Linux "/a/b" is the same as "/a/b/", "/a/b//", ..., so anyone who works with it would expect appending behaviour, as opposed to overwriting.
  • Safety: Dangerous things (erasing an existing value of fs::path) should be much harder to do than safe/non-destructive things (just adding to an existing value of fs::path). In other words, the current behaviour relies only on somebody's manually checking what is going on. If you forgot to write a check (whether a path starts from /) - you are out of luck.
  • Finally: I do not think a person who just wants to overwrite a path, will use / instead of =.

My primary question, however, is what the supposed solution is? Please, imagine p2 path comes to you as an argument of a function, it might be / or c/d or c.

Some (awful) options I see:

  1. I can just remove the first if in sources of libcxx:
  path& operator/=(const path& __p) {
    if (__p.is_absolute()) {
      __pn_ = __p.__pn_;
      return *this;
    }
  1. Continue using operator/ - but if I want more compliant with Linux behaviour I need to have some checks?

  2. Use concat, operator+= - but it will not add separator / in case of fs::path("a") + "b".

Ideally I would like to overwrite the operator/...

Any suggestions?

DimanNe
  • 1,791
  • 3
  • 12
  • 19

2 Answers2

2

It's not counterintuitive because you are not concatenating string, You are appending one path to another. fs::path{"/"} does not represent the character / nor the folder separator delimiter. It represents the root path. The behavior of the path::append (and operator/(path)) is that if the second operand is an absolute path then the result is the second operand (here it can be made a case that an exception should have been thrown instead).

If you want to append the directory separator you can do this:

fs::path p1 = "/q/b";
fs::path p2 = "./"; // <-- relative path

std::cout << (p1 / p2).lexically_normal() << std::endl;

// or this
std::cout << p1.concat("/").lexically_normal() << std::endl;

The path::lexically_normal is there to normalize the result so that for the general case you don't get weird paths like /q/b/./ or /q/b//.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • Of course, I can see their reasoning (who was designing the class), from my experience, however, the default was chosen wrongly. I think so, because standard ought to correspond/describe and fit well the world. The world (and here I mean Linux) behaves differently. In Linux it **is** a concatenation of strings. – DimanNe Apr 08 '20 at 16:43
  • Also, I clarified a bit my question. The problem is that p1 and p2 are arguments of a function. I cannot prepend "." to p2 always. What if p2 is "a"? I could remove leading / from p2 if p2 is absolute, but it means the complexity will be doubled... And it also means I cannot use very convenient operator/ ... :( – DimanNe Apr 08 '20 at 16:46
0

Due to the safety concerns I described in the question (it is just dangerous to have destructive/overwriting behaviour in so innocent looking operation - path1 / path2), I decided that the best option will be to just fork the current std::filesystem::path as a new type and rectify the code of operator/.

Here you can find one-header implementation, almost all of which is taken from from clang's libcxx.

DimanNe
  • 1,791
  • 3
  • 12
  • 19