Title
directory_iterator and recursive_directory_iterator are not C++20 ranges
Status
c++23
Section
[fs.class.directory.iterator][fs.class.rec.dir.itr]
Submitter
Barry Revzin

Created on 2020-08-27.00:00:00 last changed 4 months ago

Messages

Date: 2021-10-14.09:56:08

Proposed resolution:

This wording is relative to N4885.

  1. Edit [fs.filesystem.syn], header <filesystem> synopsis, as indicated:

    […]
    namespace std::filesystem {
      […]
    
      // [fs.dir.itr.nonmembers], range access for directory iterators
      directory_iterator begin(directory_iterator iter) noexcept;
      directory_iterator end(const directory_iterator&) noexcept;
    
      […]
    
      // [fs.rec.dir.itr.nonmembers], range access for recursive directory iterators
      recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept;
      recursive_directory_iterator end(const recursive_directory_iterator&) noexcept;
    
      […]
    }
    
    
    namespace std::ranges {
      template<>
      inline constexpr bool enable_borrowed_range<filesystem::directory_iterator> = true;
      template<>
      inline constexpr bool enable_borrowed_range<filesystem::recursive_directory_iterator> = true;
    
      template<>
      inline constexpr bool enable_view<filesystem::directory_iterator> = true;
      template<>
      inline constexpr bool enable_view<filesystem::recursive_directory_iterator> = true;
    }
    
    
  2. Edit [fs.dir.itr.nonmembers] as indicated:

    -1- These functions enable range access for directory_iterator.

    directory_iterator begin(directory_iterator iter) noexcept;
    

    -2- Returns: iter.

    directory_iterator end(const directory_iterator&) noexcept;
    

    -3- Returns: directory_iterator().

  3. Edit [fs.rec.dir.itr.nonmembers] as indicated:

    -1- These functions enable use of recursive_directory_iterator with range-based for statements.

    recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept;
    

    -2- Returns: iter.

    recursive_directory_iterator end(const recursive_directory_iterator&) noexcept;
    

    -3- Returns: recursive_directory_iterator().

Date: 2021-10-14.00:00:00

[ 2021-10-14 Approved at October 2021 virtual plenary. Status changed: Voting → WP. ]

Date: 2021-06-15.00:00:00

[ 2021-06-23; Reflector poll ]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

Date: 2021-05-15.00:00:00

[ 2021-05-17, Tim provides wording ]

Both MSVC and libstdc++'s end already take its argument by value, so the wording below just does that. Any discussion about changing or removing the poison pills is probably better suited for a paper.

Date: 2021-02-15.00:00:00

[ 2021-02-22, Barry Revzin comments ]

When we do make whichever of the alternative adjustments necessary such that range<directory_iterator> is true, we should also remember to specialize enable_borrowed_range for both types to be true (since the iterator is the range, this is kind of trivially true).

Date: 2020-09-15.00:00:00

[ 2020-09-06; Reflector prioritization ]

Set priority to 3 during reflector discussions.

Date: 2020-08-27.00:00:00

std::filesystem::directory_iterator and std::filesystem::recursive_directory_iterator are intended to be ranges, but both fail to satisfy the concept std::ranges::range.

They both opt in to being a range the same way, via non-member functions:

directory_iterator begin(directory_iterator iter) noexcept;
directory_iterator end(const directory_iterator&) noexcept;

recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept;
recursive_directory_iterator end(const recursive_directory_iterator&) noexcept;

This is good enough for a range-based for statement, but for the range concept, non-member end is looked up in a context that includes ([range.access.end]/2.6) the declarations:

void end(auto&) = delete;
void end(const auto&) = delete;

Which means that non-const directory_iterator and non-const recursive_directory_iterator, the void end(auto&) overload ends up being a better match and thus the CPO ranges::end doesn't find a candidate. Which means that {recursive_,}directory_iterator is not a range, even though const {recursive_,}directory_iterator is a range.

This could be fixed by having the non-member end for both of these types just take by value (as libstdc++ currently does anyway) or by adding member functions begin() const and end() const.

A broader direction would be to consider removing the poison pill overloads. Their motivation from P0970 was to support what are now called borrowed ranges — but that design now is based on specializing a variable template instead of providing a non-member begin that takes an rvalue, so the initial motivation simply no longer exists. And, in this particular case, causes harm.

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2021-10-14 09:56:08adminsetmessages: + msg12120
2021-10-14 09:56:08adminsetstatus: voting -> wp
2021-09-29 12:57:28adminsetstatus: ready -> voting
2021-06-23 14:16:45adminsetmessages: + msg11957
2021-06-23 14:16:45adminsetstatus: new -> ready
2021-05-18 09:45:04adminsetmessages: + msg11815
2021-05-18 09:45:04adminsetmessages: + msg11814
2021-02-22 17:56:51adminsetmessages: + msg11694
2020-09-06 13:07:36adminsetmessages: + msg11473
2020-08-27 00:00:00admincreate