I have some duplicated code in which I read from two streams,
{
std::ifstream ifs("A.dat");
... code ...
}
{
std::ifstream ifs("B.dat");
... same code ...
}
I wanted to unify both in one loop. The first reaction is to do this:
for(auto ifs : {ifstream("A.dat"), ifstream("B.dat")})
{
... code ...
}
However it doesn't compile because the type is not copyable, so I tried this:
for(auto& ifs : {ifstream("A.dat"), ifstream("B.dat")})
{
... code ...
}
that doesn't work because ifs
inside the loop is const
. (a const ifstream
cannot be used.)
This didn't work either, I think for the same reason:
for(auto&& ifs : {ifstream("A.dat"), ifstream("B.dat")})
At the end of course I ended up doing this.
#include<iostream>
int main(){
for(auto& name : {"A.dat", "B.dat"})
{
std::ifstream ifs(name);
... code ...
}
But I am still curious if it is possible to have range for-loop directly with a type like std::ifstream
?
std::ifstream streams[2];
streams[0].open("A.dat");
streams[1].open("B.dat");
for (auto &stream:streams)
{
// ...
}
In c++, everything is possible.
Imagine being able to iterator a collection of any kind of stream, like this:
int main()
{
std::string buffer;
for (auto& stream : streams(std::istringstream("hello world"),
std::istringstream("funky chicken"),
std::ifstream("foo.txt")))
{
while (stream >> buffer)
{
std::cout << buffer << std::endl;
}
}
}
Well now you can. Behold: a polymorphic temporary container and iterator for any istream
.
A similar technique will work for any arbitrary collection of objects that share a common polymorphic interface.
#include <iostream>
#include <fstream>
#include <sstream>
#include <utility>
#include <tuple>
#include <array>
namespace detail {
template<class Interface>
struct iface_iter
{
using value_type = Interface;
using reference = Interface&;
using internal_p = value_type * const *;
iface_iter(internal_p pp) : _pp(pp) {}
reference operator*() const {
return **_pp;
}
iface_iter& operator++() {
++_pp;
return *this;
}
bool operator==(const iface_iter& r) const {
return _pp == r._pp;
}
bool operator!=(const iface_iter& r) const {
return !(*this == r);
}
internal_p _pp;
};
template<class CommonType, class...Streams>
struct common_sequence
{
using common_type = CommonType;
using iterator = iface_iter<common_type>;
constexpr static std::size_t size() { return sizeof...(Streams); }
using storage_type = std::tuple<Streams...>;
using pointer_array = std::array<common_type*, size()>;
common_sequence(Streams...streams)
: _storage(std::move(streams)...)
, _pointers(build_pointers(std::make_index_sequence<size()>(), _storage))
{}
common_sequence(common_sequence&& r)
: _storage(std::move(r._storage))
, _pointers(build_pointers(std::make_index_sequence<size()>(), _storage))
{
}
common_sequence& operator=(common_sequence&& r)
{
_storage = std::move(r._storage);
_pointers = build_pointers(std::make_index_sequence<size()>(), _storage);
}
template<std::size_t I>
using stream_type = std::tuple_element_t<I, storage_type>;
template<std::size_t...Is>
static constexpr
pointer_array build_pointers(std::index_sequence<Is...>,
std::tuple<Streams...>& tup)
{
return pointer_array {
static_cast<common_type*>(&static_cast<stream_type<Is>&>(std::get<Is>(tup)))...
};
}
iterator begin() const {
return { _pointers.data() };
}
iterator end() const {
return { _pointers.data() + size() };
}
mutable storage_type _storage;
pointer_array _pointers;
};
}
template<class CommonBase, class...Things>
auto make_common_sequence(Things&&...ts)
{
return detail::common_sequence<CommonBase, std::decay_t<Things>...>(std::move(ts)...);
}
template<class...Streams>
auto streams(Streams&&...strs)
{
return make_common_sequence<std::istream>(std::move(strs)...);
}
struct base
{
virtual void foo() = 0;
};
struct d1 : base
{
void foo() override { std::cout << "d1::foo" << std::endl; }
};
struct d2 : base
{
void foo() override { std::cout << "d2::foo" << std::endl; }
};
template<class...Ts>
auto bases(Ts&&...ts)
{
return make_common_sequence<base>(std::move(ts)...);
}
int main()
{
std::string buffer;
for (auto& stream : streams(std::istringstream("hello world"),
std::istringstream("funky chicken"),
std::ifstream("foo.txt")))
{
while (stream >> buffer)
{
std::cout << buffer << std::endl;
}
}
for (auto& f : bases(d1(), d2(), d1(), d2()))
{
f.foo();
}
return 0;
}
expected output:
hello
world
funky
chicken
... plus whatever is in foo.txt ...
d1::foo
d2::foo
d1::foo
d2::foo
Of course, if we don't require polymorphism, a simple template variadic argument iterator will suffice:
template<class F, class...Things>
void apply_to_all(F f, Things... things)
{
using expand = int[];
void(expand{ 0,
(f(things), 0)...
});
}
int main()
{
std::string buffer;
apply_to_all([&](auto& stream)
{
while (stream >> buffer)
{
std::cout << buffer << std::endl;
}
},
std::istringstream("hello world"),
std::istringstream("funky chicken"),
std::ifstream("foo.txt"));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With