3

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?

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • A `directory_iterator` **is** a valid iterator, and you can use it in the normal ways (increment, compare, dereference). It can **also** be used in the way you show, as a range. – BoBTFish Oct 01 '20 at 12:35
  • @BobTFish In this example what is being iterator over? How would this be written without a range-based for loop, so I can understand. – Zebrafish Oct 01 '20 at 12:36
  • Seem stuck on the need for a container, and not what the iterator design pattern is intended to do. Like provide a common interface for iterating. Your channel up/down buttons are iterators. Your first code block is also nonsensical. `exists()` returns a bool, but you don't care about it or make any checks against it. You also do nothing with the file size. And as the answers point out, you seem to misunderstand just what is required for a range-based for loop as well. – sweenish Oct 01 '20 at 12:36

3 Answers3

3

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';
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • The iterator is iterating over std::filesystem::directory_entry, so how would I iterate over this manually? directory_entry doesn't have begin() and end(). – Zebrafish Oct 01 '20 at 12:44
  • @Zebrafish `directory_iterator` is a proper iterator, much like any other iterator. Dereferencing it returns a `directory_entry` structure, just like any other iterator returns some other type on dereferencing (like `std::vector::iterator` returns an `int`). – Some programmer dude Oct 01 '20 at 12:52
  • Yes, it's just that usually an iterator isn't the second part of a range-based loop after the ":", is it? – Zebrafish Oct 01 '20 at 13:05
  • @Zebrafish That's because the overloaded `std::begin` and `std::end` functions. The right-hand-side of the `:` in a range-for doesn't need to have `begin` and `end` member functions, only overloaded `begin` and `end` functions in the right scope. And as mentioned in my answer, the overloads for `directory_iterator` are made with range-for iteration in mind, and are implemented in a way to allow the iterator to be used as such. – Some programmer dude Oct 01 '20 at 13:09
1

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."

Botje
  • 26,269
  • 3
  • 31
  • 41
1

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.

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
  • 1
    If it's iterating over directory_entry why doesn't directory_entry have begin() and end() to compare with? How would this be written without the range-based loop, so I can understand? – Zebrafish Oct 01 '20 at 12:39
  • @Zebrafish `directory_entry` doesn't expose another way (at the moment of writing) to iterate over its files. `operator*` on this iterator does something OS specific. – Hatted Rooster Oct 01 '20 at 12:44
  • It *doesn't* overload `begin` and `end`: they are in the `std::filesystem` namespace, and that seems to make it a bit of a hassle to get ADL right. – Marcus Müller Jan 25 '23 at 19:29