2664. operator/ (and other append) semantics not useful if argument has root

Section: 31.12.6.5.3 [fs.path.append], 31.12.6.8 [fs.path.nonmember] Status: C++17 Submitter: Peter Dimov Opened: 2014-05-30 Last modified: 2017-07-30

Priority: 2

View all other issues in [fs.path.append].

View all issues with C++17 status.

Discussion:

In a recent discussion on the Boost developers mailing list, the semantics of operator / and other append operations were questioned:

In brief, currently p1 / p2 is required to concatenate the lexical representation of p1 and p2, inserting a preferred separator as needed.

This means that, for example, "c:\x" / "d:\y" gives "c:\x\d:\y", and that "c:\x" / "\\server\share" gives "c:\x\\server\share". This is rarely, if ever, useful.

An alternative interpretation of p1 / p2 could be that it yields a path that is the approximation of what p2 would mean if interpreted in an environment in which p1 is the starting directory. Under this interpretation, "c:\x" / "d:\y" gives "d:\y", which is more likely to match what was intended.

I am not saying that this second interpretation is the right one, but I do say that we have reasons to suspect that the first one (lexical concatenation using a separator) may not be entirely correct. This leads me to think that the behavior of p1 / p2, when p2 has a root, needs to be left implementation-defined, so that implementations are not required to do the wrong thing, as above.

This change will not affect the ordinary use case in which p2 is a relative, root-less, path.

[17 Jun 2014 Rapperswil LWG will investigate issue at a subsequent meeting.]

[2016-02, Jacksonville]

Beman to provide wording.

[2016-06-13, Beman provides wording and rationale]

Rationale: The purpose of the append operations is to provide a simple concatenation facility for users wishing to extend a path by appending one or more additional elements, and to do so without worrying about the details of when a separator is needed. In that context it makes no sense to provide an argument that has a root-name. The simplest solution is simply to require !p.has_root_name(). The other suggested solutions IMO twist the functions into something harder to reason about yet any advantages for users are purely speculative. The concatenation functions can be used instead for corner cases.

[Apr 2016 Issue updated to address the C++ Working Paper. Previously addressed File System TS]

[2016-07-03, Daniel comments]

The same wording area is touched by LWG 2732.

Previous resolution [SUPERSEDED]:

This wording is relative to N4594.

  1. Change 31.12.6.5.3 [fs.path.append] path appends as indicated:

    path& operator/=(const path& p);

    -?- Requires: !p.has_root_name().

    -2- Effects: Appends path::preferred_separator to pathname unless:

    • an added directory-separator would be redundant, or

    • an added directory-separator would change a relative path to an absolute path [Note: An empty path is relative. — end note], or

    • p.empty() is true, or

    • *p.native().cbegin() is a directory-separator.

    Then appends p.native() to pathname.

    -3- Returns: *this.

    template <class Source>
      path& operator/=(const Source& source);
    template <class Source>
      path& append(const Source& source);
    template <class InputIterator>
      path& append(InputIterator first, InputIterator last);

    -?- Requires: !source.has_root_name() or !*first.has_root_name(), respectively.

    -4- Effects: Appends path::preferred_separator to pathname, converting format and encoding if required (31.12.6.3 [fs.path.cvt]), unless:

    • an added directory-separator would be redundant, or

    • an added directory-separator would change a relative path to an absolute path, or

    • source.empty() is true, or

    • *source.native().cbegin() is a directory-separator.

    Then appends the effective range of source (31.12.6.4 [fs.path.req]) or the range [first, last) to pathname, converting format and encoding if required (31.12.6.3 [fs.path.cvt]).

    -5- Returns: *this.

  2. Change 31.12.6.8 [fs.path.nonmember] path non-member functions as indicated:

    path operator/(const path& lhs, const path& rhs);

    -?- Requires: !rhs.has_root_name().

    -13- Returns: path(lhs) /= rhs.

[2016-08-03 Chicago]

After discussion on 2732, it was determined that the PR for that issue should be applied to this issue before it is accepted. That PR changes all the path appends to go through operator/=, so only one requires element remains necessary.

Fri AM: Moved to Tentatively Ready

Proposed resolution:

This wording is relative to N4606, and assumes that the PR for 2732 is applied.

  1. Change 31.12.6.5.3 [fs.path.append] as indicated:
    path& operator/=(const path& p);

    -?- Requires: !p.has_root_name().

    -2- Effects: Appends path::preferred_separator to pathname unless:

    1. — an added directory-separator would be redundant, or
    2. — an added directory-separator would change a relative path to an absolute path [Note: An empty path is relative. — end note], or
    3. p.empty() is true, or
    4. *p.native().cbegin() is a directory-separator.

    Then appends p.native() to pathname.

    -3- Returns: *this.

  2. Change 31.12.6.8 [fs.path.nonmember] p13 as indicated:
    path operator/(const path& lhs, const path& rhs);

    -13- ReturnsEffects: Equivalent to return path(lhs) /= rhs;.