Klaus
Klaus

Reputation: 25613

avoid to cast element pointer type of a container

I have a function where I need vector of pointers to Base and a Derived type. And I have a function which should be able to handle with both container types, because it simply call only virtual functions all defined in Base.

If given the element pointers direct is no problem like:

void Func(Base*);
Derived* d;
Func(d);  // works perfect

But having a container type, it becomes a ugly and dangerous. If and only if the container type is fully equivalent for both pointer types the cast will work. That is typically true because all known implementations of standard containers implement pointer types as derived from void* to minimize the footprint as I know.

#include <iostream>
#include <vector>

class Base
{   
    public: virtual void Func() {std::cout << "Base" << std::endl; }
};  

class Derived: public Base
{   
    public:
        void Func() override { std::cout << "Derived" << std::endl; }
        void MoreFunc(){ std::cout << "SomeMoreFunc" << std::endl; } 
};  

// This function should "eat" also vector<Derived*>
// this can be simply done by using templates, 
// but results in doubling the code in memory while
// two instances will be created with exact the same
// content and they will not be optimized away ( gcc ).
void DoSomething( std::vector<Base*>& vec )
{   
    for ( auto& el: vec ) { el->Func();}   
}   

int main()
{   
    std::vector< Base*> vecBase;
    vecBase.push_back( new Base );
    vecBase.push_back( new Base );

    std::vector< Derived*> vecDerived;
    vecDerived.push_back( new Derived );
    vecDerived.push_back( new Derived );

    DoSomething( vecBase );
    DoSomething( (std::vector<Base*>&)vecDerived ); // that cast I want to avoid!

    for ( auto& el: vecDerived ) { el->MoreFunc(); }   
}   

How can I avoid the bad cast for the vector element type here? Do a full copy of the container is a alternative but waste also code and run time.

Is there a "typical" solution for such problems?

And yes, DoSomething inside is more complex as given in that minimal example. So simply removing for() to foreach/lambda has no chance and also calling the function inside DoSomething from the outside did not fit my real world problem here. And also it must be kept in mind, that for_each itself will generate more template instances here which is exactly the opposite of what I want to avoid.

Upvotes: 1

Views: 43

Answers (1)

skypjack
skypjack

Reputation: 50540

You can use std::for_each from <algorithm> and a lambda to do that:

auto func = [](Base *ptr) { ptr->Func(); };
std::for_each(vecBase.begin(), vecBase.end(), func);
std::for_each(vecDerived.begin(), vecDerived.end(), func);

Or define your function as it follows:

void DoSomething(Base* ptr) {   
    ptr->Func();
}

And use it with std::for_each:

std::for_each(vecBase.begin(), vecBase.end(), DoSomething);
std::for_each(vecDerived.begin(), vecDerived.end(), DoSomething);

No templates at all in both cases.
Well, to be honest, no templates in your code. std::for_each is a function template, but DoSomething won't. Not sure it's what you were looking for.


Edit after comments

The best way to avoid templates at all is to get the loop out of DoSomething.
Any function that accepts a Base * will be fine in this case.
On the other side, if you want to iterate within your function, probably you can't get away with either passing a container (template) or passing two iterators (template) or doing fancy casts based on assumptions (risky).

Note that the member function data of a vector returns a pointer to the underlying array. If combined with size, you would have all what you need to iterate over the elements.
Anyway, in your case, Base ** and Derived ** are different beasts and you can't simply pass a Derived ** to a function that accepts a Base **.

Any attempt to use functions from the standard library (as an example std::for_each) will probably end in using a template function somehow.

That's because std::vector<Base *> and std::vector<Derived *> are different types.

Upvotes: 2

Related Questions