Reputation: 21728
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
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
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
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