Home > Software engineering >  Can an iterator such as std::filesystem::directory_iterator be used outside of a loop?
Can an iterator such as std::filesystem::directory_iterator be used outside of a loop?

Time:03-19

I'm trying to understand how things like iterators can be used in c , and would specifically like to understand std::filesystem::directory_iterator better.

I understand the straight forward examples like this:

#include <iostream>
#include <filesystem>
#include <string>

void doSomething(std::string filename)
{
    std::cout << filename;
};

int main()
{
    auto iterator = std::filesystem::directory_iterator("c:/somefolder");
    for (auto& i : iterator)
    {
        doSomething(std::filesystem::path(i.path()).filename().string());
    }
}

But lots of legacy code isn't made in a tight loop like that. Is it possible to use the directory_iterator in a way somewhat similar to WinAPI FindNextFile()?

Something similar to this:

std::string getNextFilename(std::string path)
{
    // Notice the actual filesystem access code is neatly packed away in a replaceable function suitable for a HAL.
    static auto iterator = std::filesystem::directory_iterator(path);
    return std::filesystem::path(iterator.path()).filename().string();
}

int main()
{
    while (std::string fileName = getNextFilename("c:/somefolder"))
    {
        doSomething(fileName);
    }

    // or

    std::string fileFirst  = getNextFilename("c:/somefolder");
    std::string fileSecond = getNextFilename("c:/somefolder");
    std::string fileLast   = getNextFilename("c:/somefolder");

}

Please answer:

  1. In general for iterators, can or can't they be used like this, and why?
  2. In specific how to perform this sort of directory lookup of one file/directory at a time.

edit1: Clarified title.

CodePudding user response:

It is easily possible to manually increment iterators (since that is what the range based for loop does as well). However you need to adjust the other code accordingly, as there is no operator bool for std::string. One possible solution (only slightly modifying the original code, which still includes all its issues) could look like this (using std::optional<std::string> to enable returning an "end" condition):

#include <iostream>
#include <filesystem>
#include <string>
#include <optional>

void doSomething(std::string filename)
{
    std::cout << filename;
};

std::optional<std::string> getNextFilename(std::string path)
{
    static auto iterator = std::filesystem::directory_iterator(path);
    if (iterator != std::filesystem::directory_iterator()) {
        auto filename = std::filesystem::path(iterator->path()).filename().string();
          iterator; // advance iterator to next entry in directory
        return filename; // uses implicit constructor of `std::optional`
    } else {
        return {}; // return empty optional if we reached end of directory
    }
}

int main()
{
    while (auto fileName = getNextFilename("c:/somefolder"))
    {
        doSomething(*fileName); // dereference optional to get value
    }

    // or

    auto fileFirst  = getNextFilename("c:/somefolder");
    auto fileSecond = getNextFilename("c:/somefolder");
    auto fileLast   = getNextFilename("c:/somefolder");

}

CodePudding user response:

Assuming that you want to have a more or less drop-in replacement for FindFirstFile/FindNextFile, the solution could loook like this:

struct HANDLE{
    std::filesystem::directory_iterator it;
};
bool FindNextFile(HANDLE &handle, std::string& output) {
    if(handle.it == std::filesystem::directory_iterator{}) return false;
    output=handle.it->path().string();
    handle.it  ;
    return true;
}
HANDLE FindFirstFile(std::string input, std::string& output) {
    HANDLE h;
    h.it=std::filesystem::directory_iterator{input};
    FindNextFile(h,output);
    return h;
}
  • Related