15

The following piece of code aims to strip the first part of a path in case it exists:

#include <filesystem>

std::filesystem::path strip_prefix(std::filesystem::path p)
{      
  if (auto it{p.begin()}; it != p.end())
  {
    ++it;
    return std::filesystem::path(it, p.end());
  }

  return p;
}

(See: https://godbolt.org/z/wkXhcw)

I was surprised to find out this does not work. The code does not compile since the path constructor only takes iterators that iterate over character sequences. I can see the use of that, but why limit construction to only those kind of iterators? In my opinion it is counter intuitive to not support constructing a path from its own iterators. As far as I know most other STL types do support this idiom.

What would be an efficient implementation to achieve the same goal, other than completely reconstructing a new path?

Update: in this context, I found the following discussion relevant/amusing: http://boost.2283326.n4.nabble.com/boost-filesystem-path-frustration-td4641734.html. I agree with Dave here. I think seeing a path as a container of path elements is a very natural way to look at it (from a programmer's perspective).

Ton van den Heuvel
  • 10,157
  • 6
  • 43
  • 82

1 Answers1

6

The simplest solution to the concatenating the segments to make a new path is just std::accumulate().

For your particular use case, I'd do something like this:

std::filesystem::path strip_prefix(std::filesystem::path p)
{
    if(p.empty()) return p;
    return std::accumulate(std::next(p.begin()), p.end(), 
                           std::filesystem::path{}, std::divides{});
}

As for why there isn't a constructor (or maybe a free function) to do this? I don't know. This seems like a need that comes up a fair bit when working with paths, but the committee does tend to be reluctant to add convenience functions to standard classes if the same result can be achieved by a call to a standard algorithm.

T.C.
  • 133,968
  • 17
  • 288
  • 421
Parker Coates
  • 8,520
  • 3
  • 31
  • 37
  • 1
    I disagree that this type of range constructor would be classified as superfluous convenience function on the grounds that literally every other container (except `std::valarray` and `std::array`) has a range constructor. – rubenvb Dec 12 '18 at 14:16
  • @rubenvb Oh, _I'm_ not saying it would be superfluous. I'm just theorising that maybe the committee would. – Parker Coates Dec 12 '18 at 14:22
  • 1
    The complicated thing with `path` is that it consumes character iterators, but yields segment iterators. I can't think of another `std` container with this behaviour. – Parker Coates Dec 12 '18 at 14:32
  • I don't believe `path` is technically a container (and what you say might have a lot to do with it). Though it does already have character iterator range constructors, one could argue a segment iterator constructor wouldn't be so hard to add such that one can do `path p{other_path.begin(), other_path.end()-1};` or similar shenanigans. – rubenvb Dec 12 '18 at 14:52
  • @rubenvb, for the external API I think it would be more natural to see a path as a container of path segments. Relevant other discussion I just found: http://boost.2283326.n4.nabble.com/boost-filesystem-path-frustration-td4641734.html – Ton van den Heuvel Dec 12 '18 at 20:59
  • @T.C.Interesting. Which corner cases are those? – Parker Coates Dec 14 '18 at 12:53
  • NTFS alternate data streams. If you have a filename that is `b:ads`, `/` will treat the `b:` part as a root-name and do bad things. Compare [LWG issue 3070](https://wg21.link/lwg3070). – T.C. Dec 15 '18 at 01:15
  • 1
    +1 for `std::divides`. Yep, that's intuitive! ;-) (You don't have to explain it to me; I understand why that works. I simply find it an amusing example of how well-meaning C++ library design inevitably lead to weird constructs.) – Adrian McCarthy Jul 28 '21 at 21:13