Is std::filesystem::directory_iterator really an iterator?

2.4k Views Asked by At

Something doesn't make to sense. According to what I've read you use std::filesystem like this:

#include <iostream>
#include <filesystem>
#include <string>
  
int main()
{
    auto iterator = std::filesystem::directory_iterator("c:/somefolder");
    for (auto& i : iterator)
    {
        i.exists();
        i.file_size();
    }
}

I read the range-based loop as "for each i in iterator, call i.file_size()". With standard containers in C++ this is how it looks, for example a standard vector container.

std::filesystem::directory_iterator seems inconsistent. An iterator is supposed to point to elements in a container, but with std::filesystem::directory_iterator it seems to be a container itself, right? Each i in a range-based loop is a "directory_entry".

If:

std::vector<int> container;
for (auto& i : container)

Is equivalent to:

std::vector<int> container;
for (auto it = std::vector<int>::iterator; it != container.end(); it++)

What's:

for (auto i : iterator)

Equivalent to?

What is happening in the range-based loop above? Is it wrong to read that loop as "for each i in iterator"? The i value is a std::filesystem::directory_entry, but what is being iterated over in the loop? What container?

3

There are 3 best solutions below

0
On

The range-based for loop uses overloads of std::begin and std::end:

directory_iterator begin( directory_iterator iter ) noexcept;
directory_iterator end( const directory_iterator& ) noexcept;

Note how calling begin on a directory_iterator returns the directory_iterator itself, while end ignores its argument and "Returns a default-constructed directory_iterator, which serves as the end iterator."

4
On

According to this reference it's a LegacyInputIterator. So yes it's a "true" iterator.

There are overloaded begin and end functions. The begin function returns the iterator unmodified, and the end function doesn't use the argument and instead returns a default-constructed iterator. They exists just to support the range-for loop.

So if you have:

auto iterator = std::filesystem::directory_iterator("c:/somefolder");

then

iterator == begin(iterator) && std::filesystem::directory_iterator() == end(iterator)

will be true.


Note that since begin will return the iterator unmodified, even after you do iterator++ the condition iterator == begin(iterator) will be true.


To iterate over a directory "manually" you just do it almost like any other iterator:

for (auto iterator = std::filesystem::directory_iterator("c:/somefolder");
     iterator != std::filesystem::directory_iterator();
     iterator++)
{
    // Use the iterator
    std::cout << "The path is " << iterator->path() << '\n';
}
3
On

A ranged-based for loop doesn't always need a container to work. It can also loop over a range which is what it's doing in this case.

A ranged-based for loop is structured as follows:

auto && __range = range_expression ;
for ( ; begin_expr != end_expr ; ++begin_expr) {

    range_declaration = *__begin;
    loop_statement

} 

Where begin_expr and end_expr are :

Otherwise, begin_expr is begin(__range) and end_expr is end(__range), which are found via argument-dependent lookup (non-ADL lookup is not performed).

Since directory_iterator overloads begin and end it functions just like a normal LegacyInputIterator.

for (auto i : iterator)

We're not actually "looping over the iterator" as you'd do with a container because the iterator itself is no container as you noted, we're accessing the iterator and incrementing it on repeat until we reach end(). The container here is directory_entry.