Fedor
Fedor

Reputation: 21087

C++ directory item iteration without exceptions

In C++17 it became easy to iterate over the items in some directory dir:

for ( auto& dirEntry: std::filesystem::directory_iterator(dir) )
{
  if ( !dirEntry.is_regular_file() ) continue;
...

Unfortunately this way may throw exceptions, which I want to avoid in my program.

The iteration without throwing exceptions is also possible:

std::error_code ec;
const std::filesystem::directory_iterator dirEnd;
for ( auto it = std::filesystem::directory_iterator( dir, ec ); !ec && it != dirEnd; it.increment( ec ) )
{
  if ( !it->is_regular_file( ec ) ) continue;
...

but it is much wordier in C++. For example, I cannot use range based for. And this larger code size is really significant for me since I have a lot of places with iteration. Is there a way to simplify the code iterating directory items and still avoid exceptions?

Upvotes: 1

Views: 912

Answers (1)

Fedor
Fedor

Reputation: 21087

I think one can create a safe wrapper iterator, which operator ++ will not throw an exception, as follows

// object of this struct can be passed to range based for
struct safe_directory
{
    std::filesystem::path dir;
    std::error_code & ec;
};

//iterator of directory items that will save any errors in (ec) instead of throwing exceptions
struct safe_directory_iterator
{
    std::filesystem::directory_iterator it;
    std::error_code & ec;
    safe_directory_iterator & operator ++() { it.increment( ec ); return * this; }
    auto operator *() const { return *it; }
};

safe_directory_iterator begin( const safe_directory & sd )
{
    return safe_directory_iterator{ std::filesystem::directory_iterator( sd.dir, sd.ec ), sd.ec };
}
 
std::filesystem::directory_iterator end( const safe_directory & )
{
    return {};
}

bool operator !=( const safe_directory_iterator & a, const std::filesystem::directory_iterator & b )
{
    return !a.ec && a.it != b;
}

Then it can be used in a program like that

int main()
{
    std::error_code ec;
    safe_directory sdir{ std::filesystem::current_path(), ec };

    for ( auto dirEntry : sdir )
    {
        if ( dirEntry.is_regular_file( ec ) )
            std::cout << dirEntry.path() << std::endl;
    }
}

See example in online compiler: https://gcc.godbolt.org/z/fb4qPE6Gf

Upvotes: 1

Related Questions