This will be a short post about a feature in STL that seems to be not as well-known as it should be.
Imagine we want to create a small function that collects files in the subdirectories of the current directory. So, a list that would be returned by ls */*
.
We’ll start by creating a function that returns filenames of all files in the specified directory.
std::vector<fs::directory_entry>
files_in_dir(const fs::directory_entry& dir)
{
return std::vector<fs::directory_entry>(
fs::directory_iterator{dir.path()},
fs::directory_iterator{});
}
This function creates a vector of all files in a specified directory using the STL’s filesystem library.
What we need to do now is create a function that calls files_in_dir
for all directories in the current directory and collect all the results in a single vector.
std::vector<fs::directory_entry>
files_in_subdirs()
{
std::vector<fs::directory_entry> results;
auto item = fs::directory_iterator{"."};
const auto last = fs::directory_iterator{};
for (; item != last; ++item) {
if (!item->is_directory()) {
continue;
}
auto dir_items = files_in_dir(*item);
[ Add everything from dir_items into results ]
}
return results;
}
The function should be easy to understand – it is just iterating through subdirectories of the current directory, and calls files_in_dir
to get the list of files in them.
The question that remains is how to add those collected items into the resulting vector.
We can use .insert
to insert all items from dir_items
into the results
vector:
results.insert(results.end(),
dir_items.cbegin(),
dir_items.cend());
The problem is that this will copy all the directory_entry
values from dir_items
to result
. The copies are unnecessary because dir_items
is destroyed immediately afterwards.
We could have moved everything into the results
vector.
If we didn’t know any better, we could replace this insert with a for loop that moves the elements from dir_items
to results
one by one (we would need to call reserve
before to eliminate the possibility of vector reallocations).
But we do know better – there is an iterator adaptor that returns rvalue references when it is dereferenced and it is aptly named std::move_iterator
.
This means that we can still rely on higher abstraction functions like .insert
and still be efficient. We just need to pass a move iterator to .insert
instead of the normal one:
results.insert(results.end(),
std::make_move_iterator(dir_items.begin()),
std::make_move_iterator(dir_items.end()));
That’s it for now.