1

The conditions are:

// A hierarchy
struct Base {
    virtual void everyone_has_this() = 0;
};
struct DA : Base {
    void everyone_has_this() override {...}
};
struct DB : Base {
    void everyone_has_this() override {...}
    void only_db_has_this() {...}
};

// And some vectors
vector<DA> das (3);
vector<DB> dbs (2);

I'd love to be able to use something like:

// Foreach all of them in one loop
for(Base& o : view_as_one<Base>(das, dbs)) {
   o.everyone_has_this();
}
// Or just several types of them
for(DB& o : dbs) {
   db.only_db_has_this();
}

The question is: is it possible?
If not, what are the other ways to one loop, instead of for each container?
Important is, I don't want to get rid of the segregation and storage contigiousness.

A dynamic_cast would work, if I used a single base pointer container, but it would involve checking for the type on every iteration, and the goal is to store all the objects in contigious storages.


Edit: Alternative solution if boost is not an option or view is not enough
While I do love m.s.'s solution, because he implemented exactly the interface I asked for, I found that this interface I wanted, is not actually that flexible (can't use erase-remove). So here's a different approach:

for_each_in_tuple(std::tie(das, dbs), [](auto& cont){
   for(auto& obj : cont) {
       // do what you want
   }
   // or even
   cont.erase(std::remove_if(begin(cont), end(cont), [](auto& obj) {
       // also do what you want
   }, end(cont));
});
  • (-) Not as pretty
  • (+) The iterator doesn't have to check which range it belongs to (faster)
  • (+) It works even without virtual methods (because auto&).
  • (+) Dependency only on <tuple> => no boost & compiles faster.
  • (+) Not as restricted (e.g. can use erase-remove)
  • (?) Not sure, but looks like msvc2015 can compile too.
    Note:
    There are a plentiful of for_each tuple algorithms. I took this one:
    http://pastebin.com/6e8gmZZA
iwat0qs
  • 146
  • 3
  • 10
  • Which functions are shared through `Base` actually? – πάντα ῥεῖ Jul 23 '16 at 13:39
  • I would have another go at this question as I have not got a clue what you are trying to do – Ed Heal Jul 23 '16 at 13:40
  • πάντα ῥεῖ, Say `virtual void foo() = 0`; – iwat0qs Jul 23 '16 at 13:41
  • You need a combined container (references) with an iterator jumping from one container to the other for your `view_as_one` –  Jul 23 '16 at 13:41
  • @iwat0qs You may opt to hold your vectors a `std::unique_ptr` or `std::shared_ptr`. – πάντα ῥεῖ Jul 23 '16 at 13:48
  • @πάνταῥεῖ No segregation then (?). The goal was to be able to foreach a bunch of containers, or several, or just one arbitrarily. – iwat0qs Jul 23 '16 at 13:51
  • 1
    @DieterLücking will answers given in that question work in OP case? From what I seen all answers require either same type for both containers or that references to them could be convertible between each other which is not the case for OP. – Revolver_Ocelot Jul 23 '16 at 13:52
  • @πάνταῥεῖ I've edited my question. The linked solution(s) doesn't work. Still duplicate? – iwat0qs Jul 23 '16 at 15:27

2 Answers2

1

This can be implemented using boost::transform_iterator and boost::join.

The following code uses Luc Dantons variadic join implementation:

#include <vector>
#include <tuple>
#include <iostream>
#include <utility>

#include <boost/iterator/transform_iterator.hpp>
#include <boost/range/join.hpp>
#include <boost/range/iterator_range.hpp>


namespace ns {

// for ADL purposes
using std::begin;
using std::end;

struct join_type {
    template<class C>
    auto operator()(C&& c) const
    -> decltype(boost::make_iterator_range(begin(c), end(c)))
    {
        return boost::make_iterator_range(begin(c), end(c));
    }

    template<typename First, typename Second, typename... Rest>
    auto operator()(First&& first, Second&& second, Rest&&... rest) const
    -> decltype( (*this)(boost::join(boost::make_iterator_range(begin(first), end(first)), boost::make_iterator_range(begin(second), end(second))), std::forward<Rest>(rest)...) )
    {
        return (*this)(boost::join(boost::make_iterator_range(begin(first), end(first)), boost::make_iterator_range(begin(second), end(second))), std::forward<Rest>(rest)...);
    }
};

constexpr join_type join {};

} // ns


template <typename T>
struct ReturnTRef
{
    T& operator()(T& x) const { return x;};
};

template <typename T, typename Tuple, std::size_t... Indices>
auto view_as_one_impl(Tuple&& tuple, std::index_sequence<Indices...>)
{
    ReturnTRef<T> returnTRef;
    return ns::join(boost::make_iterator_range(boost::make_transform_iterator(begin(std::get<Indices>(tuple)), returnTRef), boost::make_transform_iterator(end(std::get<Indices>(tuple)), returnTRef))...);
}

template <typename B, typename... Args>
auto view_as_one(Args&&... args)
{
    return view_as_one_impl<B>(std::forward_as_tuple<Args...>(args...), std::index_sequence_for<Args...>{});
}


struct Base {virtual ~Base(){}; virtual void talk() = 0;};
struct DA : Base {void talk() override { std::cout << "DA" << std::endl;} };
struct DB : Base {void talk() override { std::cout << "DB" << std::endl;} };


int main()
{
    std::vector<DA> das(3);
    std::vector<DB> dbs(2);

    for(Base& x : view_as_one<Base>(das, dbs))
    {
        x.talk();
    }
}

output

DA
DA
DA
DB
DB

live example

Community
  • 1
  • 1
m.s.
  • 16,063
  • 7
  • 53
  • 88
0

Yes, it is possible. Just write some code to do this.

Simply implement view_as_one so that it meets the requirements for a C++ library container; or at least the minimum requirements needed to implement this functionality. Its constructor stores the references to the constituent, underlying containers. Implement its iterator class as iterating, in sequence, over each constituent container. Implement view_as_one<T>::begin() and view_as_one<T>::end(). Done.

I would expect to see this as a homework assignment in a typical "Advanced C++" programming class.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • You just need to write class compatible with range-for loop. You do not need to implement Container requirements. (If you are looking in future, you might want to make it a Range) – Revolver_Ocelot Jul 23 '16 at 13:45
  • @Sam Yeah, I understand that I would need a custom iterator for that, because there are no conversions between vector::iterator and vector::iterator. But is it still possible, if view_as_one would take a variadic number of containers? From what I believe, view_as_one would construct a sort of recursive template struct ? – iwat0qs Jul 23 '16 at 13:45
  • @iwat0qs technically you can do that if all containers `value_type` have a common base. You can make a recursive template, or use commongly used in `tuple` multiple inheritance trick. – Revolver_Ocelot Jul 23 '16 at 13:47
  • Yes, it is still possible if `view_as_one` takes a variadic list of containers. All that the variadic constructor needs to do is to save the pointers to the constituent containers (passed by reference) into the internal `std::vector` list of constituent containers. The implementation of `view_as_one` is agnostic as to the number of the constituent containers. If I were to give this homework assignment to my students, I would fail anyone who codes `view_as_one` for a specific number of containers, like 2 or 3. The right implementation must support any number of containers. – Sam Varshavchik Jul 23 '16 at 13:51
  • 1
    So, if I pass a `list`, `deque` and `vector`, what type stored pointers will be? I would expect to store list of containers as `tuple`, not `vector`. – Revolver_Ocelot Jul 23 '16 at 13:55
  • 1
    A vector of smart pointers to an abstract class that implements `begin()` and `end()`. `view_as_one` constructs an implementing class for each constituent container (pushing each one into the vector) implementing `begin()` and `end()` as returning smart pointers to an abstract iterator class that implements `operator*`, `operator++`, and `clone()` virtual methods. `view_as_one` then defines its iterator class and/or `begin()`/`end()` in the obvious manner. – Sam Varshavchik Jul 23 '16 at 14:21