Win32 File Name Iteration Boost Way
A Port of ::FindFirstFile to Boost.Range and Boost.Foreach
Introduction
I was inspired by the excellent article, Win32 File Name Iteration STL Way. I think the problem of the article is that the handle of ::FindFirstFile
is managed by iterators. After combating Boost.Range for months, I realized that Boost.Range could remove that complexity. The result was named find_file_range
, which is the direct translation from ::FindFirstFile
to C++.
Requirements
- Microsoft Visual C++ .NET Version 7.1
- Boost C++ Libraries Version 1.33.0 (no compilation required unless building demo)
- Boost.Foreach
- Boost.RangeEx
find_file_range
find_file_range
is a model of Single Pass Range whose value_type
is WIN32_FIND_DATA
. If you are not familiar with Boost.Range concepts, you could regard it as something like a Container which provides Input Iterators:
find_file_range ffrng("*.*");
typedef boost::range_result_iterator<find_file_range>::type iter_t;
for (iter_t it = boost::begin(ffrng), last = boost::end(ffrng); it != last; ++it) {
std::cout << it->cFileName << std::endl;
}
If you iterate find_file_range
twice, the behavior is undefined. This code proves find_file_range
is a thin wrapper of ::FindFirstFile
, but seems somewhat clumsy. Why must we write the idiom hundreds of times? If you resent it, it will come.
Foreach will Come
Boost.Foreach (accepted into Boost, but not yet part of the release) provides foreach
that is missing from C++:
find_file_range ffrng("*.*");
BOOST_FOREACH (WIN32_FIND_DATA& data, ffrng) {
std::cout << data.cFileName << std::endl;
}
Is this a new language? No, It's C++. The implementation is magical. If you are interested in it, check Conditional Love: FOREACH Redux written by Eric Niebler.
Filtering
One class, one responsibility. find_file_range
doesn't offer any filtering using dwFileAttributes
of WIN32_FIND_DATA
. Instead, Adaptable Predicates named find_file_is_xxx
are provided. They are available for boost::make_filter_range
, which is a part of Boost.RangeEx:
find_file_range ffrng("*.*");
BOOST_FOREACH (
WIN32_FIND_DATA& data,
boost::make_filter_range(ffrng, boost::not1(find_file_is_dots()))
)
{
std::cout << data.cFileName << std::endl;
}
std::not1
is completely broken by reference-to-reference problem, so you must use boost::not1
from Boost.Functional.
Transforming
As a mark of respect for the original article, find_file_construct
template that makes a model of Adaptable Unary Functor is provided for those who want to get cFileName
of WIN32_FIND_DATA
as a string
object. It is available for boost::make_transform_range
:
find_file_range ffrng("*.*");
BOOST_FOREACH (
std::string const& filename,
boost::make_transform_range(ffrng, find_file_construct<std::string>())
)
{
std::cout << filename << std::endl;
}
find_file_construct
template takes a type Sequence. If your string
type is not a Sequence like CString
, you could use find_file_stringize
instead.
Chain of Range Adaptors
The chain of filtering and transforming is also available:
find_file_range ffrng("*.*");
BOOST_FOREACH (
std::string const& filename,
boost::make_transform_range(
boost::make_filter_range(
boost::make_filter_range(
ffrng,
boost::not1(find_file_is_hidden())
),
boost::not1(find_file_is_directory())
),
find_file_stringize<std::string>()
)
)
{
std::cout << filename << std::endl;
}
This chain is lazy and functional, but unreadable. And so Boost.RangeEx provides Range Adaptors:
find_file_range ffrng("*.*");
BOOST_FOREACH (
std::string const& filename,
ffrng |
boost::adaptor::filter(boost::not1(find_file_is_hidden())) |
boost::adaptor::filter(boost::not1(find_file_is_directory())) |
boost::adaptor::transform(find_file_construct<std::string>())
)
{
std::cout << filename << std::endl;
}
This is rather "procedural" and shows the future of C++.
Points of Interest
find_file_range
is just like a disposable container and doesn't offer any additional functionalities unlike Boost.Filesystem, but it doesn't demand any additional resources. Boost.Range concepts are wide-open to such classes. In fact, find_file_range
depends on the fact that clients don't iterate twice, while boost::iterator_range
depends on the fact that holding iterators are valid. You could easily make many classes conform to the concepts. I hope this article helps:
std::string inputs; {
boost::copy(make_istream_range<char>(std::cin), std::back_inserter(inputs));
}
std::cout << inputs << std::endl;