Georg P.
Georg P.

Reputation: 3164

C++ Concatenate containers of polymorphic objects

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?

Upvotes: 16

Views: 1303

Answers (5)

Vittorio Romeo
Vittorio Romeo

Reputation: 93304

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".

Upvotes: 16

Stephan Lechner
Stephan Lechner

Reputation: 35154

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;
}

Upvotes: 4

Chris Drew
Chris Drew

Reputation: 15334

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);

Upvotes: 2

bipll
bipll

Reputation: 11940

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.)

Upvotes: 1

Chad K
Chad K

Reputation: 952

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

Upvotes: 4

Related Questions