4

This program:

#include <iostream>
#include <filesystem>

int main()
{
    std::filesystem::path p1("c:\\");
    std::filesystem::path p2("c:/");
    if (p1.has_parent_path())
        std::cout << "Parent path of " << p1 << " is " << p1.parent_path() << std::endl;
    if (p2.has_parent_path())
        std::cout << "Parent path of " << p2 << " is " << p2.parent_path() << std::endl;
}

Produces this output:

Parent path of "c:\\" is "c:\\"
Parent path of "c:/" is "c:/"

(EDIT: There was confusion about my use of forward slash so I updated this code to show the same thing happens regardless of which path separators you use on Windows)

This makes no sense to me. How can a directory be the parent of itself? What is the point of even having a "has_parent" function if it's never going to return false?

Most importantly: If I am writing code that recursively searches up a directory tree looking for a file, what is the best/most reliable way to detect that I've hit the root folder and should stop?

(I'm using Visual Studio 2019 in C++17 language mode, if that matters)

Joe
  • 5,394
  • 3
  • 23
  • 54
  • 1
    "_to detect that I've hit the root folder and should stop_" from the above, and [NutCracker's answer](https://stackoverflow.com/a/58201491/2096401) it looks like checking if `p.parentPath()` is the same as `p` should work. _Presumably_, `has_parent_path()` of `isolated_file.name` would return `false`. (Both ideas untested). – TripeHound Oct 02 '19 at 12:59

2 Answers2

3

Actually there is no output in the godbolt example, because the test is run on GCC on a posix environment, where "C:\\" is not a root directory, so it is seen as a strange directory/file name and its parent is empty, but with the correct posix root path "/" it would have given the output, as the OP is observing. So no, parent_path() of a root directory ("/" or "C:\\") is not empty, so has_parent_path() is true on all implementations of std::filesystem I know of.

The actual standard says: "Returns: *this if has_relative_path() is false, otherwise a path whose generic format pathname is the longest prefix of the generic format pathname of *this that produces one fewer element in its iteration." and relative_path() is everything after the root_path(), so there is no relative_path() in this case, so parent_path() returns *this and has_parent_path() returns true, as in the example of the OP.

My guess with the question of why the standard chose this behaviour is, that this is what cd .. does on every OS, if you are already at the root, you stay at the same root.

Indeed one possible way of detecting the end is: (!p.has_parent_path() || !p.has_relative_path()) or maybe even !p.has_relative_path(), depending on if you want to end with an empty path if it was rootless.

Gulrak
  • 726
  • 7
  • 10
  • 1
    Thank you. That makes sense. In my own code my workaround was to add a check to see if parent_path() == root_path(), since that was also the case in this condition. I have no idea if my idea or your idea is more reliable or if they are the same. – Joe Oct 04 '19 at 15:45
0

Function bool has_parent_path() const; checks whether the path returned from the path parent_path() const; function is empty.

Furthermore, for the path parent_path() const; function, the standard says:

Returns the root directory of the generic-format path. If the path (in generic format) does not include root directory, returns path().

On Linux systems, where the root path is /, and on Windows systems, where the root path is C:\\, parent_path() function would return empty string so, consequently, has_parent_path() function would return false.

Check this code from here:

#include <iostream>
#include <experimental/filesystem>

namespace fs = std::experimental::filesystem;

int main() {
    for (fs::path p : {"/var/tmp/example.txt", "/", "C:\\", "/var/tmp/."}) {
        std::cout << "The parent path of " << p
                  << " is " << p.parent_path() << '\n';
        std::cout << "Has parent path: " << p.has_parent_path() << '\n';
    }

    return 0;
}

Pay attention to spelling out the Windows root directory. It is not c:/ but C:\\.

NutCracker
  • 11,485
  • 4
  • 44
  • 68
  • So the behavior is different on Windows and Linux? – Hengqi Chen Oct 02 '19 at 13:02
  • No, I missed one thing so please check again – NutCracker Oct 02 '19 at 13:03
  • The root of the C drive is C:\, but \ is the escape character for string literals. You could write it as `R"(C:\)"` – Caleth Oct 02 '19 at 13:52
  • Actually "c:/" is perfectly valid to use as the root drive on windows. Forward slash it not an escape character and is accepted by file functions. I generally prefer it to backslahs – Joe Oct 02 '19 at 13:53
  • @Caleth i know but the OP wrote forward slash – NutCracker Oct 02 '19 at 13:54
  • I am the OP. If I change my code to use the two backslashes the behavior is identical – Joe Oct 02 '19 at 13:58
  • To be clear I did read the standard before I posted this. Your statement that a path of c:\\ would return an empty parent path on Windows is incorrect. My question is not "why does my code behave as the standard says it should" but rather, "why does the standard have this nonsensical behavior". – Joe Oct 02 '19 at 14:00
  • I'm sorry I don't know what to do with that. Is there a "run" button somewhere that I'm missing? When I run that exact code on Visual Studio 2019 on my machine (I just copy-pasted it) it tells me that the path you typed there *does* have a parent which is itself – Joe Oct 02 '19 at 14:10
  • The third column shows the output of the program. You don't need to run it – NutCracker Oct 02 '19 at 14:44
  • Actually it does not. The third column shows me the following 3 sentences: "ASM generation compiler returned: 0", "Execution build compiler returned: 0", and "Program returned 0". It does not show me any std::cout output. – Joe Oct 02 '19 at 14:52
  • Because there is no output. If condition is not satisfied – NutCracker Oct 02 '19 at 14:52
  • Well regardless, when I run it on the real MSVC compiler, I get what I get. – Joe Oct 02 '19 at 14:59