1

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?

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 2
    Sure. Write a routine like you have above, and call it from everywhere. – Marshall Clow May 31 '21 at 16:03
  • 1
    How do you expect the range based for loop to indicate an error? You don't have access to iterator and you cannot throw exceptions. You may be able to write a wrapper class that treats a directory entry as the end iterator (and possibly provides error info) though. – fabian May 31 '21 at 16:09
  • 2
    Notice than both versions may throw `std::bad_alloc`. (/me dislikes filesystem interface). – Jarod42 May 31 '21 at 16:09
  • 1
    My only advice would be ``using`` aliases...? ``using DirIt = std::filesystem::directory_iterator;`` You probably thought of that already, but I'm mentioning just in case. – Davi May 31 '21 at 17:04

1 Answers1

1

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

Fedor
  • 17,146
  • 13
  • 40
  • 131