Choose recursive/non-recursive directory iterator based on a run-time condition

138 Views Asked by At

I want to use methods std::filesystem::directory_iterator or std::filesystem::recursive_directory_iterator depending on a boolean. The code would be something like:

some_type dirIterator = isRecursive ? std::filesystem::recursive_directory_iterator : std::filesystem::directory_iterator;

for (const std::filesystem::directory_entry& entry : dirIterator(path))
{
    // common code for the two iterators
}

How can I accomplish this? If there are different approaches I would like to know all to choose the one I like more, but I think I prefer the simpler one :-)

I tried to use a function pointer but I do not know the "common" return type of the two iterators:

??? (*dirIterator) (const std::filesystem::path & dirPath);
2

There are 2 best solutions below

2
Jan Schultke On BEST ANSWER

recursive_directory_iterator and directory_iterator are distinct types, and they are not covariant. If you try to select one of two with the conditional ? A : B operator, there is a problem, since A and B are unrelated types.

Template-based solution

However, both types of iterators share a common interface. Most importantly, they are iterable. This suggests that you could use templates to solve the problem.

namespace fs = std::filesystem; // applies to all code in this answer

template <typename DirectoryIterator>
void doStuff(DirectoryIterator iterator) {
    // TODO: consider constraining this function with std::enable_if
    for (const fs::directory_entry& entry : iterator) {
        // ...
    }
}

// alternatively, make doStuff a generic lambda:
auto doStuff = [](auto iterator) {
    for (const fs::directory_entry& entry : iterator) {
        // ...
    }
}

// ...
if (isRecursive) doStuff(fs::recursive_directory_iterator(path));
else             doStuff(fs::directory_iterator(path));

Non-template solutions

Another solution is to put whatever you were going to do in the loop into a lambda:

auto visitEntry = [](const fs::directory_entry& entry) {
    // ...
};

if (isRecursive) {
    for (const fs::directory_entry& entry : fs::recursive_directory_iterator(path)) {
        visitEntry(entry);
    }
}
else {
    for (const fs::directory_entry& entry : fs::directory_iterator(path)) {
        visitEntry(entry);
    }
}

This produces mild code duplication, but it's conceptually simpler than the template.

A clever solution by commenter @Turtlefight is to use recursive_diretory_iterator in both cases, and disable recursion after each iteration with disable_recursion_pending:

auto iterator = fs::recursive_directory_iterator(path);
for (const fs::directory_entry& entry : iterator) {
    // ...
    if (!isRecursive) iterator.disable_recursion_pending();
}
0
bolov On

There are a few ways to implement this:

auto do_on_dir(const std::filesystem::directory_entry& entry) {}

auto do_on_all_folders(const std::string& path, bool isRecursive) {
    if (isRecursive) {
        for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) {
            do_on_dir(entry);
        }
    }
    else {
        for (const auto& entry : std::filesystem::directory_iterator(path)) {
            do_on_dir(entry);
        }
    }
}

auto do_on_all_folders_recursive(const std::string& path) {}

auto test() {
    do_on_all_folders("path", false);
    do_on_all_folders("path", true);
}
auto do_on_dir(const std::filesystem::directory_entry& entry) {}

auto do_on_all_folders(const std::string& path) {
    for (const auto& entry : std::filesystem::directory_iterator(path)) {
        do_on_dir(entry);
    }
}

auto do_on_all_folders_recursive(const std::string& path) {
    for (const auto& entry :
             std::filesystem::recursive_directory_iterator(path)) {
        do_on_dir(entry);
    }
}

auto test() {
    do_on_all_folders("path");
    do_on_all_folders_recursive("path");
}
auto do_on_dir(const std::filesystem::directory_entry& entry) {}

template <bool Recursive>
auto do_on_all_folders(const std::string& path) {
    if constexpr (Recursive) {
        for (const auto& entry :
             std::filesystem::recursive_directory_iterator(path)) {
            do_on_dir(entry);
        }
    } else {
        for (const auto& entry : std::filesystem::directory_iterator(path)) {
            do_on_dir(entry);
        }
    }
}

auto test() {
    do_on_all_folders<true>("path");
    do_on_all_folders<false>("path");
}