Generic way to use fs::recursive_directory_iterator() and fs::directory_iterator()

Viewed 557

I need to iterate over folder either recursively or not (given the boolean parameter). I have discovered there is fs::recursive_directory_iterator() and also fs::directory_iterator(). In Java, I would expect them to implement the same interface or share the common ancestor so that I could substitute the needed one. But for some reason the two iterators do not share the common ancestor, forcing the to write the code like:

  if (recursive_) {
    path = recursive_iterator_->path();
    recursive_iterator_++;
  } else {
    path = plain_iterator_->path();
    plain_iterator_++;
  }

I cannot believe this is how it is supposed to work. I also initially assumed there are some options to turn off recursion for recursive_directory_iterator but seems no any between std::filesystem::directory_options.

The value is not known at the compile time. I think it should be possible to use something like a closure or even subclass with virtual method but looks a bit like overkill.

Should I simply use conditionals switching between the two iterators as needed, or there are better approaches?

2 Answers

implement the same interface

They do. They are both InputIterators, that dereference to const std::filesystem::directory_entry&.

C++ avoids virtual by default.

You can use boost::any_range to type erase the recursiveness.

template <typename... Args>
auto make_directory_range(bool recursive, Args... args) {
    return recursive
        ? boost::make_iterator_range(fs::recursive_directory_iterator(args...), fs::recursive_directory_iterator()) | boost::adaptors::type_erased()
        : boost::make_iterator_range(fs::directory_iterator(args...), fs::directory_iterator());
}

using iterator_t = decltype(make_directory_range(true).begin());

auto range = make_directory_range(recursive_, args...);
iterator_t iterator = range.begin();
iterator_t end = range.end();

The usual way of dealing with a static polymorphism situation like this is to use a helper template:

template<class F,class ...AA>
void for_each_file(F f,bool rec,AA &&...aa) {
  const auto g=[&](auto end) {
    std::for_each(decltype(end)(std::forward<AA>(aa)...),
                  end,std::move(f));
  };
  if(rec) g(fs::recursive_directory_iterator());
  else g(fs::directory_iterator());
}
std::size_t count(const fs::path &d,bool rec) {
  std::size_t n=0;
  for_each_file([&](fs::directory_entry) {++n;},rec,d);
  return n;
}

This approach does have limitations: it makes it harder to break out of the “loop”, for example.

Related