Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For range loop for a non copyable type, is it possible?

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?

like image 897
alfC Avatar asked May 12 '16 23:05

alfC


2 Answers

std::ifstream streams[2];

streams[0].open("A.dat");
streams[1].open("B.dat");

for (auto &stream:streams)
{
    // ...
}
like image 67
Sam Varshavchik Avatar answered Nov 15 '22 13:11

Sam Varshavchik


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"));
}
like image 1
Richard Hodges Avatar answered Nov 15 '22 13:11

Richard Hodges