3

I have a problem with upcasting and downcasting in my program. I have a vector<Child> that is passed to a function that expects const vector<Parent>& pp. There are no problems up to here (EDIT: apparently there are! See the comments). But, now I want to pass pp to a function that expects const vector<Child>& cc, Which I cannot do.

How should I do this, while at the same time I do not give the functions the ability to modify the original classes? Could you list various ways of doing this, preferably with their pros and cons?

Eliad
  • 894
  • 6
  • 20
  • 6
    There’s already a problem in the first step, contrary to what you’ve said. You simply cannot do that. – Konrad Rudolph Apr 15 '15 at 06:43
  • 2
    You should consider using a `vector<`smart_ptr`>` with whatever variant of smart_ptr makes sense for your usage (`unique_ptr` or `shared_ptr`), then you can still store `Child` objects "in" the `vector` and pass it to a function operating on the elements polymorphically. – Tony Delroy Apr 15 '15 at 06:52
  • @TonyD, Thanks. I decided to use smart pointers, as you suggested. – Eliad Apr 15 '15 at 08:07

4 Answers4

5

There is a thing called variation. It comes in a 3 flavors:

  • invariation - even though B extends A, T<B> not extends T<A>,
  • covariation - when B extends A, then T<B> extends T<A>,
  • contravariation - when B extends A, then T<A> extends T<B>.

When it comes to C++ templates you end up with invariation. Even though by name it looks the same: vector<Parent> and vector<Child> those are 2 different types.

If you look at what is generated by compiler both of them operate on types which could potentially have different sizes. Since C++ rely on knowledge on object size (e.g. when it calculates position of object in an array) type e.g. Child[] cannot be casted to Parent[] because position of some object might be miscalculated. For the same reasons templates act in an invariant way: compiler cannot guess when it would and when it wouldn't be safe to perform such casting.

So it is up to you fix that and you have some options here. One would be making function that take that parameter template as well:

template<T>
void performAction(vector<T> objects) {
  // ...
}

Other would be replacing values with a (smart) pointers - they would handle polymorphism easily.

EDIT:

To specify what I meant in the last sentence: you could simply use vector< unique_ptr<Parent> > or vector< shared_ptr<Parent> > to store any instance of Parent (including Child), so you won't have to perform any casting of the container.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
  • 2
    Regarding your last paragraph: `vector` and `vector` are just as unrelated and you would have to settle for using `vector` everywhere. – molbdnilo Apr 15 '15 at 08:00
  • I indeed meant that you can simply use `vector` to store both `Parent` and `Child` instances, so there will be no problem with casting. But, yes I didn't say that clearly. – Mateusz Kubuszok Apr 15 '15 at 08:35
  • I suspected that (and should have said that I suspected that instead of going knowitall). +1. – molbdnilo Apr 15 '15 at 08:42
0

Even if Child is derived from Parent or the other way round, vector<Child> and vector<Parent> are unrelated, they are different types.

Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
0

You can have a template function template func(vector vec) { //do something based on the type of object passed }. Vectors are containers for objects so for a function which is expecting a vector &pp, if we pass vector && cc will not work and the code won't even compile. We can use a code similar to:

class A
{
    int i;
};

class B : public A
{
    int j;
    int k;
};


template<class T> void f(vector<T> &p)
{
    //can handle both types now
}

int main()
{
    B b1;
    A a1;


    vector<A> vectorA;
    vectorA.push_back(a1);

    vector<B> vectorB;  
    vectorB.push_back(b1);

    f<B>(vectorB);

    f<A>(vectorA);
    return 0;
}
Nirmal Thakur
  • 232
  • 3
  • 10
  • Huh? This seems to be a direct contradiction of what several other answers and comments say about the C++ type system. – David K Apr 15 '15 at 13:20
  • @DavidK How is that so? Can you explain a little more on what you mean? – Nirmal Thakur Apr 16 '15 at 03:05
  • As far as I know, and other answers and comments seem to say this to, you cannot use `vector` where `vector` is expected. Sure, you can use `Child` where `Parent` is expected, because `Child` is derived from `Parent`; but `vector` is _not_ derived from `vector`. Have you written a function that took a parameter like `const vector &&pp`, and passed a `vector` to it, and did it work as desired? – David K Apr 16 '15 at 03:57
  • @DavidK You are right. It will not work because this does not apply to containers so one correct solution is using a template function as suggested in the answer to this question. – Nirmal Thakur Apr 16 '15 at 06:44
0

You can't. It's impossible.

Definitely don't do this:

template<typename TBase, typename TChild>
const std::vector<TBase*>& downcast(const std::vector<TChild*>& children)
{
    static_assert(std::derived_from<TChild, TBase>);
    return *reinterpret_cast<const std::vector<TBase*>*>(&children);
}
Spongman
  • 9,665
  • 8
  • 39
  • 58