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.