16

Assume I have a virtual base class and some derived concrete classes:

class Base { ... }
class DerivedA : public Base { ... }
class DerivedB : public Base { ... }
class DerivedC : public Base { ... }

And somewhere I have vectors of objects of each derived class:

std::vector<DerivedA> my_a;
std::vector<DerivedB> my_b;
std::vector<DerivedC> my_c;

Now, quite often I need to iterate over all elements in all three vectors and exercise the base class interface. I could write three for-loops, doing exactly the same in each. But obviously that's a far from optimal solution.

Is there a clever way to concatenate the vectors into a common container with base-class pointers/references, such that I need to iterate only once? Or any other idea how to solve this elegantly?

Georg P.
  • 2,785
  • 2
  • 27
  • 53

5 Answers5

16

There's no need for polymorphism in your current situation. You could simply use a variadic template + higher-order function to iterate over the vectors. Here's a C++17 solution using a fold expression:

template <typename F, typename... Vectors>
void for_all_vectors(F&& f, Vectors&&... vs)
{
    (std::for_each(std::forward<Vectors>(vs).begin(), 
                   std::forward<Vectors>(vs).end(), 
                   f), ...);
}

Usage:

int main()
{
    std::vector<A> my_a;
    std::vector<B> my_b;
    std::vector<C> my_c;

    for_all_vectors([](const auto& x){ something(x); }, my_a, my_b, my_c);
}

live example on wandbox


In C++11/14 you can replace the fold expression with for_each_argument:

template <typename TF, typename... Ts>
void for_each_argument(TF&& f, Ts&&... xs)
{
    return (void)std::initializer_list<int>{
        (f(std::forward<Ts>(xs)), 0)...};
}

template <typename F, typename... Vectors>
void for_all_vectors(F&& f, Vectors&&... vs)
{
    for_each_argument([&f](auto&& v)
    { 
        std::for_each(v.begin(), v.end(), f);
    }, std::forward<Vectors>(vs)...);
}

live example on wandbox

I explain the idea behind this snippet and expand upon it in this CppCon 2015 talk: "for_each_argument explained and expanded".

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 2
    @AndyG: good point, I'm forwarding the vectors in the fold expression example now. Regarding `F&&`: it could accept non-movable temporaries *(e.g. lambda with generalized capture of something which is non-movable)* while `F` couldn't. – Vittorio Romeo Jul 07 '17 at 12:10
  • 1
    Ok, that seems pretty advanced to me. I'm afraid people will complain heavily at the code review... The C++17 solution looks quite beautiful, however, I can only use C++14 for now. I'll try to get more comfortable with the syntax, and then probably use this solution. Anyway, thanks a lot - for now you get +1, maybe later I'll accept! – Georg P. Jul 08 '17 at 12:20
  • @GeorgP I think you are right to be cautious. I think it depends how generic your code needs to be. How often are you likely to be needing this kind of functionality and how many vectors of objects do you actually have. If you actually only have three vectors and you only do this once then perhaps a variadic template is unnecessary. – Chris Drew Jul 10 '17 at 07:54
4

Just have a pointer to the base class. You can't have a vector of type base and put derived classes into it because they might not be the same size, same functions, ect.

So what I would do is create a vector or type base* and then you can concatenate the pointers of the derived class.

Probably would look something like:

vector<base*> v;
v.push_back(&derivedClassVariableA);
v.push_back(&derivedClassVariableB);

Then as long as the functions you are looking to use are virtual in the base and are defined in the derived, you should be good to go

Chad K
  • 832
  • 1
  • 8
  • 19
  • 1
    So if I understand you right, I'd have to write a method that consecutively iterates over each vector, takes a pointer to each element, casts it to a base-class pointer, pushes the pointer to a new vector and in the end returns the new vector. And outside of this method I'd iterate over the elements of the new vector. That was also my first idea of solving it, but it seems very manual and probably also not the best in terms of performance. – Georg P. Jul 08 '17 at 12:11
  • It depends on the methods you need to access. If they're the same but defined differently and also virtual in the base class, you don't need to do anything special. If not, then you can't do it this way. For example if your base class had something like "Print()", and it's defined in the derived classes, it's fine. In that case, you can just push back the pointers to each object into a vector of the base class – Chad K Jul 10 '17 at 15:51
4

A simple solution would be to use a template function that iterates over the members of a vector and calls the respective function:

class Base {
public:
    virtual int getX() = 0;
};

class Derived1 : public Base {
public:
    int x1=1;
    virtual int getX() { return x1; };
};

class Derived2 : public Base {
public:
    int x2=2;
    virtual int getX() { return x2; };
};

template<typename T>
void callPolymorphic(std::vector<T> &v) {
    for (T& a : v) {
        cout << a.getX() << " ";
    }
}

int main() {

    std::vector<Derived1> my_1(5);
    std::vector<Derived2> my_2(5);

    callPolymorphic(my_1);
    callPolymorphic(my_2);

    return 0;
}
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
2

I would just create a function template or a generic lambda and call it three times:

auto my_loop = [](auto& vec){
  for (auto& base : vec) {
      // do something with base...
  }
};
my_loop(my_a);
my_loop(my_b);
my_loop(my_c);
Chris Drew
  • 14,926
  • 3
  • 34
  • 54
1

On the other hand, you can manufacture your own homemade view adapter:

#include <iostream>
#include <vector>
#include <functional>
#include <iterator>

struct Base {
    virtual int f() const = 0;
    virtual ~Base() {}
};

struct D1: Base {
    int f() const { return 42; }
};

struct D2: Base {
    int f() const { return 314; }
};

template<typename T, typename... Left, typename... Right>
inline std::vector<T, Left...> operator+(std::vector<T, Left...> &&left, std::vector<T, Right...> &&right) {
    std::vector<T, Left...> retVal(std::move(left));
    using mi = std::move_iterator<typename std::vector<T, Right...>::iterator>;
    retVal.insert(retVal.end(), mi(right.begin()), mi(right.end()));
    return retVal;
}

int main() {
    std::vector<D1> v1(3);
    std::vector<D2> v2(4);
    using View = std::vector<std::reference_wrapper<const Base>>;
    View b(View(v1.cbegin(), v1.cend()) + View(v2.cbegin(), v2.cend()));
    for(Base const &item: b) std::cout << item.f() << std::endl;
}

(Note that the underlying viewed containers can be any, vectors are but for instance, but their element types should be conformant.)

bipll
  • 11,747
  • 1
  • 18
  • 32
  • It's a clever solution to be sure, but I don't like how we need to iterate over all items twice, as well as allocate extra storage for the reference wrappers. – AndyG Jul 07 '17 at 11:56
  • @AndyG sure, but note that: 1) ref wrapper is not a huge object; 2) you probably may need to iterate more that twice over it, much much more, in which case the very first iteration does not make much of a difference; 3) and I wonder, in case we really iterate over it at most once, is there a compiler/STL implementation combination to optimize away the very construction of the container. – bipll Jul 10 '17 at 19:00
  • That's a good point so long as one's use case is read-heavy. For every write, you must reconstruct the `View` (although you could probably optimize it so long as order is unimportant). – AndyG Jul 11 '17 at 12:09
  • Ummm... do you mean reconstruct the same view? But why? Why not keep it? – bipll Jul 12 '17 at 05:54
  • Keeping it is fine if the workflow is okay with iterating over outdated information. I was merely suggesting that after an insertion into `v1` or `v2`, the end user might want the next iteration over them to incorporate the new element. Otherwise we need to either reconstruct the `View` or append another `reference_wrapper` to `b` if order does not matter. – AndyG Jul 12 '17 at 11:33