c++iteratorc++17std-filesystem

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


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

Solution

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