This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++20 status.

### 3096. `path::lexically_relative` is confused by trailing slashes

Section: 31.12.6.5.11 [fs.path.gen] Status: C++20 Submitter: Jonathan Wakely Opened: 2018-04-04 Last modified: 2021-02-25

Priority: 2

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

View all issues with C++20 status.

Discussion:

`filesystem::proximate("/dir", "/dir/")` returns `"."` when `"/dir"` exists, and `".."` otherwise. It should always be `"."` because whether it exists shouldn't matter.

The problem is in `filesystem::path::lexically_relative`, as shown by:

>     path("/dir").lexically_relative("/dir/.");  // yields ""
>     path("/dir").lexically_relative("/dir/");   // yields ".."

The two calls should yield the same result, and when iteration of a `path` with a trailing slash gave "." as the final element they did yield the same result. In the final C++17 spec the trailing slash produces an empty filename in the iteration sequence, and `lexically_relative` doesn't handle that correctly.

[2018-04-10, Jonathan comments]

There are more inconsistencies with paths that are "obviously" equivalent to the human reader:

>     path("a/b/c").lexically_relative("a/b/c")    // yields "."
>     path("a/b/c").lexically_relative("a/b/c/")   // yields ".."
>     path("a/b/c").lexically_relative("a/b/c/.")  // yields ""
>     path("a/b/c/").lexically_relative("a/b/c")   // yields ""
>     path("a/b/c/.").lexically_relative("a/b/c")  // yields "."
>     path("a/b/c/.").lexically_relative("a/b/c/") // yields "../."

I think the right solution is:

1. when counting `[b, base.end())` in bullet (4.2) handle empty filename elements (which can only occur as the last element, due to a trailing slash) equivalently to dot elements; and

2. add a new condition for the case where `n == 0` and `[a, end())` contains no non-empty elements, i.e. the paths are equivalent except for final dots or a final slash, which don't introduce any relative difference between the paths.

[2018-06-18 after reflector discussion]

Priority set to 2

[2018-08-23 Batavia Issues processing]

Status to Tentatively Ready

[2018-11, Adopted in San Diego]

Proposed resolution:

This wording is relative to N4727.

1. Edit 31.12.6.5.11 [fs.path.gen] as indicated:

   >     path lexically_relative(const path& base) const;

   > > -3- Returns: `*this` made relative to `base`. Does not resolve (31.12.6 [fs.class.path]) symlinks. Does not first normalize (31.12.6.2 [fs.path.generic]) `*this` or `base`.

   > > -4- Effects: If `root_name() != base.root_name()` is `true` or `is_absolute() != base.is_absolute()` is `true` or `!has_root_directory() && base.has_root_directory()` is `true`, returns `path()`. Determines the first mismatched element of `*this` and `base` as if by:

   > > >     auto [a, b] = mismatch(begin(), end(), base.begin(), base.end());

   > > Then,

   > > 1. (4.1) — if `a == end()` and `b == base.end()`, returns `path(".")`; otherwise

   > > 2. (4.2) — let `n` be the number of filename elements in `[b, base.end())` that are not dot or dot-dot or empty, minus the number that are dot-dot. If `n<0`, returns `path()`; otherwise

   > > 3. (4.?) — if `n == 0` and `(a == end() || a->empty())`, returns `path(".")`; otherwise

   > > 4. (4.3) — returns an object of class `path` that is default-constructed, followed by […]