3013. (recursive_)directory_iterator construction and traversal should not be noexcept

Section: 30.11.12.1 [fs.dir.itr.members], 30.11.13.1 [fs.rec.dir.itr.members], 30.11.14.3 [fs.op.copy], 30.11.14.19 [fs.op.is_empty] Status: Tentatively Ready Submitter: Tim Song Opened: 2017-08-23 Last modified: 2017-11-03

Priority: 0

View other active issues in [fs.dir.itr.members].

View all other issues in [fs.dir.itr.members].

View all issues with Tentatively Ready status.

Discussion:

Constructing a (recursive_)directory_iterator from a path requires, at a minimum, initializing its underlying directory_entry object with the path formed from the supplied path and the name of the first entry, which requires a potentially throwing memory allocation; every implementation I've looked at also allocates memory to store additional data as well.

Similarly, increment() needs to update the path stored in directory_entry object to refer to the name of the next entry, which may require a memory allocation. While it might conceivably be possible to postpone the update in this case until the iterator is dereferenced (the dereference operation is not noexcept due to its narrow contract), it seems highly unlikely that such an implementation is intended (not to mention that it would require additional synchronization as the dereference operations are const).

This further calls into question whether the error_code overloads of copy and is_empty, whose specification uses directory_iterator, should be noexcept. There might be a case for keeping the noexcept for is_empty, although that would require changes in all implementations I checked (libstdc++, libc++, and Boost). copy appears to be relentlessly hostile to noexcept, since its specification forms a path via operator/ in two places (bullets 4.7.4 and 4.8.2) in addition to the directory_iterator usage. The proposed resolution below removes both.

[ 2017-11-03 Moved to Tentatively Ready after 7 positive votes for P0 on c++std-lib. ]

Proposed resolution:

This wording is relative to N4700.

  1. Edit 30.11.12 [fs.class.directory_iterator], class directory_iterator synopsis, as indicated:

    […]
    explicit directory_iterator(const path& p);
    directory_iterator(const path& p, directory_options options);
    directory_iterator(const path& p, error_code& ec) noexcept;
    directory_iterator(const path& p, directory_options options,
                       error_code& ec) noexcept;
    […]
    
    directory_iterator& operator++();
    directory_iterator& increment(error_code& ec) noexcept;
    
  2. Edit 30.11.12.1 [fs.dir.itr.members] before p2 as indicated:

    explicit directory_iterator(const path& p);
    directory_iterator(const path& p, directory_options options);
    directory_iterator(const path& p, error_code& ec) noexcept;
    directory_iterator(const path& p, directory_options options, error_code& ec) noexcept;
    

    -2- Effects: […]

    -3- Throws: As specified in 30.11.6 [fs.err.report].

    -4- [Note: […] — end note]

  3. Edit 30.11.12.1 [fs.dir.itr.members] before p10 as indicated:

    directory_iterator& operator++();
    directory_iterator& increment(error_code& ec) noexcept;
    

    -10- Effects: As specified for the prefix increment operation of Input iterators (24.2.3).

    -11- Returns: *this.

    -12- Throws: As specified in 30.11.6 [fs.err.report].

  4. Edit 30.11.13 [fs.class.rec.dir.itr], class recursive_directory_iterator synopsis, as indicated:

    […]
    explicit recursive_directory_iterator(const path& p);
    recursive_directory_iterator(const path& p, directory_options options);
    recursive_directory_iterator(const path& p, directory_options options,
                                 error_code& ec) noexcept;
    recursive_directory_iterator(const path& p, error_code& ec) noexcept;
    […]
    
    recursive_directory_iterator& operator++();
    recursive_directory_iterator& increment(error_code& ec) noexcept;
    
  5. Edit 30.11.13.1 [fs.rec.dir.itr.members] before p2 as indicated:

    explicit recursive_directory_iterator(const path& p);
    recursive_directory_iterator(const path& p, directory_options options);
    recursive_directory_iterator(const path& p, directory_options options, error_code& ec) noexcept;
    recursive_directory_iterator(const path& p, error_code& ec) noexcept;
    

    -2- Effects: […]

    -3- Postconditions: […]

    -4- Throws: As specified in 30.11.6 [fs.err.report].

    -5- [Note: […] — end note]

    -6- [Note: […] — end note]

  6. Edit 30.11.13.1 [fs.rec.dir.itr.members] before p23 as indicated:

    recursive_directory_iterator& operator++();
    recursive_directory_iterator& increment(error_code& ec) noexcept;
    

    -23- Effects: As specified for the prefix increment operation of Input iterators (24.2.3), except that: […]

    -24- Returns: *this.

    -25- Throws: As specified 30.11.6 [fs.err.report].

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

    namespace std::filesystem {
    
      […]
    
      void copy(const path& from, const path& to);
      void copy(const path& from, const path& to, error_code& ec) noexcept;
      void copy(const path& from, const path& to, copy_options options);
      void copy(const path& from, const path& to, copy_options options,
                error_code& ec) noexcept;
    
      […]
    
      bool is_empty(const path& p);
      bool is_empty(const path& p, error_code& ec) noexcept;
      
      […]
    
    }
    
  8. Edit 30.11.14.3 [fs.op.copy] as indicated:

    void copy(const path& from, const path& to, error_code& ec) noexcept;
    

    -2- Effects: Equivalent to copy(from, to, copy_options::none, ec).

    void copy(const path& from, const path& to, copy_options options);
    void copy(const path& from, const path& to, copy_options options,
              error_code& ec) noexcept;
    

    -3- Requires: […]

    -4- Effects: […]

    -5- Throws: […]

    -6- Remarks: […]

    -7- [Example: […] — end example]

  9. Edit 30.11.14.19 [fs.op.is_empty] as indicated:

    bool is_empty(const path& p);
    bool is_empty(const path& p, error_code& ec) noexcept;
    

    -1- Effects: […]

    -2- Throws: […]