Audrius Meškauskas
Audrius Meškauskas

Reputation: 21728

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

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?

Upvotes: 4

Views: 990

Answers (3)

Ayatsuki Yakumo
Ayatsuki Yakumo

Reputation: 11

Instead of using boost::adaptors::type_erased explicitly, you can also use boost::any_range with implicit type conversion. This may help with the readability of your code.

Please note that it is frequently unnecessary to use the type_erased adaptor. It is often better to use the implicit conversion to any_range. (1)

boost::any_range<fs::directory_entry, boost::single_pass_traversal_tag> iterator;

if (recursive) {
    iterator = boost::make_iterator_range(fs::recursive_directory_iterator(...), fs::recursive_directory_iterator());
}
else {
    iterator = boost::make_iterator_range(fs::directory_iterator(...), fs::directory_iterator());
}

auto begin = iterator.begin();
auto end = iterator.end();

// do whatever you want

If your fs namespace is boost::filesystem instead of std::filesystem. You can also omit the boost::make_iterator_range:

//...

if (recursive) {
    iterator = fs::recursive_directory_iterator(...);
}
else {
    iterator = fs::directory_iterator(...);
}

//...

Upvotes: 0

Davis Herring
Davis Herring

Reputation: 39778

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.

Upvotes: 0

Caleth
Caleth

Reputation: 62576

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();

Upvotes: 2

Related Questions