2936. Path comparison is defined in terms of the generic format

Section: 31.12.6.5.8 [fs.path.compare] Status: C++20 Submitter: Billy Robert O'Neal III Opened: 2017-02-21 Last modified: 2021-02-25

Priority: 2

View all issues with C++20 status.

Discussion:

Currently, path comparison is defined elementwise, which implies a conversion from the native format (implied by native() returning const string&). However, the conversion from the native format to the generic format might not be information preserving. This would allow two paths a and b to say a.compare(b) == 0, but a.native().compare(b.native()) != 0 as a result of this missing information, which is undesirable. We only want that condition to happen if there are redundant directory separators. We also don't want to change the path comparison to be in terms of the native format, due to Peter Dimov's example where we want path("a/b") to sort earlier than path("a.b"), and we want path("a/b") == path("a//////b").

Citing a Windows example, conversion to the generic format is going to have to drop alternate data streams. This might give path("a/b:ads") == path("a/b"). I think I should consider the alternate data streams as part of the path element though, so this case might be fine, so long as I make path("b:ads").native() be "b:ads". This might not work for our z/OS friends though, or for folks where the native format looks nothing like the generic format.

Additionally, this treats root-directory specially. For example, the current spec wants path("c:/a/b") == path("c:/a////b"), but path("c:/a/b") != path("c:///a/b"), because native() for the root-directory path element will literally be the slashes or preferred separators.

This addresses similar issues to those raised in US 57 — it won't make absolute paths sort at the beginning or end but it will make paths of the same kind sort together.

[2017-03-04, Kona Saturday morning]

We decided that this had seen so much churn that we would postpone looking at this until Toronto

[2017-07 Toronto Thurs Issue Prioritization]

Priority 2

[2016-07, Toronto Saturday afternoon issues processing]

Billy to reword after Davis researches history about ordering. Status to Open.

Previous resolution [SUPERSEDED]:

This wording is relative to N4640.

  1. Make the following edits to 31.12.6.5.8 [fs.path.compare]:

    int compare(const path& p) const noexcept;
    

    -1- Returns:

    — Let rootNameComparison be the result of this->root_name().native().compare(p.root_name().native()). If rootNameComparison is not 0, rootNameComparison; otherwise,

    — If this->has_root_directory() and !p.has_root_directory(), a value less than 0; otherwise,

    — If !this->has_root_directory() and p.has_root_directory(), a value greater than 0; otherwise,

    — A value greater than, less than, or equal to 0, ordering the paths in a depth-first traversal order.

    -?- [Note: For POSIX and Windows platforms, this is accomplished by lexicographically ordering the half-open ranges [begin(), end()) of this->relative_path() and p.relative_path() as follows:

    — A value less than 0, if native() for the elements of *this->relative_path() are lexicographically less than native() for the elements of p.relative_path(); otherwise,

    — a value greater than 0, if native() for the elements of *this->relative_path() are lexicographically greater than native() for the elements of p.relative_path(); otherwise,

    0.

    end note]

    -2- Remarks: The elements are determined as if by iteration over the half-open range [begin(), end()) for *this and p.

    int compare(const string_type& s) const
    int compare(basic_string_view<value_type> s) const;
    

    -3- Returns: compare(path(s))

    [Editor's note: Delete paragraph 3 entirely and merge the value_type overload with those above.]

    int compare(const value_type* s) const
    

    -4- ReturnsEffects: Equivalent to return compare(path(s));.

[2018-01-26 issues processing telecon]

Status set to 'Review'. We like the wording, but would like to see some implementation experience.

Previous resolution [SUPERSEDED]:

This wording is relative to N4659.

  1. Make the following edits to 31.12.6.5.8 [fs.path.compare]:

    int compare(const path& p) const noexcept;
    

    -1- Returns:

    — Let rootNameComparison be the result of this->root_name().native().compare(p.root_name().native()). If rootNameComparison is not 0, rootNameComparison; otherwise,

    — If this->has_root_directory() and !p.has_root_directory(), a value less than 0; otherwise,

    — If !this->has_root_directory() and p.has_root_directory(), a value greater than 0; otherwise,

    a value less than 0, iIf native() for the elements of *this->relative_path() are lexicographically less than native() for the elements of p.relative_path(), a value less than 0; otherwise,

    a value greater than 0, iIf native() for the elements of *this->relative_path() are lexicographically greater than native() for the elements of p.relative_path(), a value greater than 0; otherwise,

    0.

    -2- Remarks: The elements are determined as if by iteration over the half-open range [begin(), end()) for *this and p.

    int compare(const string_type& s) const
    int compare(basic_string_view<value_type> s) const;
    

    -3- Returns: compare(path(s))

    [Editor's note: Delete paragraph 3 entirely and merge the value_type overload with those above.]

    int compare(const value_type* s) const
    

    -4- ReturnsEffects: Equivalent to return compare(path(s));.

[2018-02-13 Billy improves wording]

The revised wording has the effect to invert the ordering of the added new bullets (2) and (3), the effect of this change is that

path("c:/").compare("c:")

compares greater, not less.

[2018-06, Rapperswil Wednesday evening]

Agreement to move that to Ready, Daniel rebased to N4750.

[2018-11, Adopted in San Diego]

Proposed resolution:

This wording is relative to N4750.

  1. Make the following edits to 31.12.6.5.8 [fs.path.compare]:

    int compare(const path& p) const noexcept;
    

    -1- Returns:

    — Let rootNameComparison be the result of this->root_name().native().compare(p.root_name().native()). If rootNameComparison is not 0, rootNameComparison; otherwise,

    — If !this->has_root_directory() and p.has_root_directory(), a value less than 0; otherwise,

    — If this->has_root_directory() and !p.has_root_directory(), a value greater than 0; otherwise,

    a value less than 0, iIf native() for the elements of *this->relative_path() are lexicographically less than native() for the elements of p.relative_path(), a value less than 0; otherwise,

    a value greater than 0, iIf native() for the elements of *this->relative_path() are lexicographically greater than native() for the elements of p.relative_path(), a value greater than 0; otherwise,

    0.

    -2- Remarks: The elements are determined as if by iteration over the half-open range [begin(), end()) for *this and p.

    int compare(const string_type& s) const
    int compare(basic_string_view<value_type> s) const;
    

    -3- Returns: compare(path(s))

    [Editor's note: Delete paragraph 3 entirely and merge the value_type overload with those above.]

    int compare(const value_type* s) const
    

    -4- ReturnsEffects: Equivalent to: return compare(path(s));.