2

Lets get a bit more specific about what I'm up to. I have an abstract Optimizer class defining an interface for various algorithms. It works by producing and consuming unknown quantities of this data-structures called Request, hence I let it use vectors of them.

class Request {
public:
    vector<double> x;
    double y;
    int id;
    // [...]
}

class Asynch_Optimizer {
public:
    virtual int generate_requests( vector<Request> & work ) = 0;
    virtual int assimilate_results( const vector<Request> & result ) = 0;
protected:
    virtual void convergence_test( const vector<Request> & population );
    // [...]
}

It turns out that a Particle_Swarm_Optimizer can use some additional fields in the Request. (I smell my fallacy here, it uses the additional fields only internally. For the Request processing machinery it has no meaning. I sub-classed to Particle just to keep everything nicely bound together)

class Particle : public Request {
public:
    vector<double> v;
    vector<double> x_min;
    double min_value;
    // [...]
}

Now I want to call the common convergence test method, implemented in the super-class from Particle_Swarm_Optimizer, where the data is in vector of Particle. Is there a more efficient way to convert to vector of Request than the construction of a new vector and copy-casting each element individually:

void opti::Particle_Swarm_Optimizer::convergence_test( const vector<Particle> & population )
{
    //TODO remove slow hack 
    vector<Request> cast_up;
    for (size_t i = 0; i < population.size(); i++) {
        cast_up.push_back( population[i] );
    }
    Asynch_Optimizer::convergence_test( cast_up );
}

I assume there are better ways to structure the data for this example. Still I'm curious if there exists a way to upcast the template-type of the container?

krauQ
  • 21
  • 1
  • You need pointers unless you want object slicing to occur. – chris Dec 01 '12 at 18:42
  • no real solution here. What I would do is refactoring and use iterator pairs instead of vector reference to pass the data around. Then you need to abstract the iterator type from the container type, google for 'any_iterator'. – sbabbi Dec 01 '12 at 18:48
  • @chris it is okay, but if OP write that he doesn't know about slicing, so let's say to him: when using polymorphism, you have to use pointers so as to reference any class behing the pointer. If you use an object ( that is not a reference but a delimited memory space) you only consider the space delimited by the object... and then happen the symptom of slicing. In the base class memory space, there is not enough room for daughter class specifities, because they have been added in memory. An object of type mother never have the space to store daughter specifities. (not sure I'm clear enough). – Stephane Rolland Dec 01 '12 at 18:48
  • 1
    @StephaneRolland, Sure you could, but, like code, don't reinvent the wheel ;) http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c – chris Dec 01 '12 at 18:50
  • @chris perfect answer to my comment :-) – Stephane Rolland Dec 01 '12 at 18:51
  • maybe we should make your link more obvious so as readers and him do consult it. – Stephane Rolland Dec 01 '12 at 18:52
  • Thanks a lot for the quick answers to my first questions here! I came across the slicing problem before but apparently forgot it. So is the iterator an often used way to work around this, or is it better to pass objects around which contain containers instead of containers with objects inside? – krauQ Dec 01 '12 at 19:07

3 Answers3

2

Cannot be done, even if you use pointers in the container. And there's a good reason why not.

Even if SubClass is a subtype of BaseClass, it is not true that vector<SubClass*> is a subtype of vector<BaseClass*>. Here's why this can't be true (for any type of container, not just vectors):

Suppose that vector<SubClass*> is treated as a subclass of vector<BaseClass*>, and suppose that there is a second class OtherSubClass also derived from BaseClass. Consider this code

// This looks logical and is type-safe.
void InsertElement(vector<BaseClass*>& container, BaseClass* element) {
   container.push_back(element);
}

int main() {
  vector<SubClass*> subclass_container;

  // Ok, fine, inserting a SubClass pointer into a vector of
  // SubClass pointers.
  SubClass subclass_obj;
  InsertElement(subclass_container, &subclass_obj);

  // But what about this? Now we're able to insert an 
  // OtherSubClass pointer into that same container!
  OtherSubClass othersubclass_obj;
  InsertElement(subclass_container, &othersubclass_obj);

  // Suddenly, we have a SubClass* that actually points at an
  // OtherSubClass object, when these two are siblings!
  SubClass* subclass_ptr = subclass_container[1];
}

So what you end up with if you adopt the idea that vector<SubClass*> should be treated as a subclass of vector<BaseClass*> is two bits of code that are apparently type-safe, but which end up allowing you to violate type-safety in a way that is undetectable to the compiler.

Tyler McHenry
  • 74,820
  • 18
  • 121
  • 166
  • Oh now I get it! On first glance it really seems so logical to expect things should work the easy way - and bugging that it wont compile. Thanks for clearly pointing out the problem with it! – krauQ Dec 01 '12 at 19:17
0

One solution is to define the optimizer as class template, and use std::enable_if to make it work with types Request or its derived classes as (untested):

template
<
  typename R, //Request or its derived classes
  typename A = typename std::enable_if
                        <
                           std::is_same<Request, R>::value ||
                           std::is_base_of<Request, R>::value 
                        >::type
>
class Asynch_Optimizer {
public:
    virtual int generate_requests( vector<R> & work ) = 0;
    virtual int assimilate_results( const vector<R> & result ) = 0;
protected:
    virtual void convergence_test( const vector<R> & population );
}
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Not quite the same thing, since you'd end up with a different `Asynch_Optimizer` class for each derived type, though that might be OK if the class is stateless. – Tyler McHenry Dec 01 '12 at 18:59
0

If you want to keep it almost like it is now, i.e. if you want runtime polymorphism, the concept of any_iterator comes to mind.

You can define a special polymorphic iterator which abstracts from both the underlying container and the underlying type. Here is a little snippet to get the idea (it misses some methods to be consider a proper iterator, but shouldn't be difficult to implement the rest by yourself)

template<class T>
class any_input_iterator : public std::iterator< std::input_iterator_tag,T> {
public:
    any_input_iterator() {}
    any_input_iterator(const any_input_iterator & other) : piter(other.piter->clone()) {}

    template<class Iter>
    any_input_iterator(Iter iter) : piter( new iterator_impl<Iter>(iter)) {}

    T& operator*() {return piter->reference();}

    bool operator==(const any_input_iterator & other) const {
        if ( typeid(*piter) != typeid(* other.piter) ) return false;
        return piter->equal(*other.piter);
    }
    any_input_iterator& operator++() {piter->advance(); return *this;}

private:
    struct iterator_base {
       iterator_base() {}
       virtual ~iterator_base() {}
       virtual void advance() = 0;
       virtual T& reference() = 0;
       virtual iterator_base* clone() = 0;
       virtual bool equal(const iterator_base & other) const = 0;
    };

   template<class InputIterator> struct iterator_impl  : public iterator_base{
       iterator_impl(InputIterator t) : m_iter(t) {}
       virtual void advance() { ++m_iter;}
       virtual T& reference() { return *m_iter;} //assuming this is implicitly castable to T&
       virtual iterator_base* clone() {return new iterator_impl(m_iter);}
       virtual bool equal(const iterator_base & other) const {
        return m_iter ==  dynamic_cast<const iterator_impl&>(other).m_iter;
       }

       InputIterator m_iter;
   };

   std::unique_ptr<iterator_base> piter;
};

Since your algorithms can not know at compile time which is the exact type on which they operate, standard iterators are of little use. Writing your algorithm in terms of this kind-of proxy iterator solves the problem.

Moreover, in my opinion is just a bad idea to write algorithms in terms of container instead of iterator pairs. I'd refactor the code in terms of iterator pairs, but if you still want to stick with vectors, you can make use of any_iterator to write an any_vector_reference class.

All these things will work only if you have sequences of the same type (if you don't, you have to work with vector of pointers).

sbabbi
  • 11,070
  • 2
  • 29
  • 57