views:

499

answers:

9

consider the following algorithm with arrays:

class MyType;
{
    // some stuff
}

class MySubType:MyType
{
    // some stuff
}

void foo(MyType** arr, int len)
{
    for (int i = 0;i<len;i++)
        // do something on arr[i]->
}

void bar()
{
    MySubType* arr[10];
    // initialize all MySubType*'s in arr
    foo(&arr, 10);
}

Nothing too fancy here. My question is - how do I do this with templates?

void foo(std::vector<MyType>& s)
{
    std::vector<MyType>::iterator i;
    for (i = s.begin(); i != s.end(); i++)
        // do stuff on *i
}

so, in bar, I can't do this:

void bar()
{
    std::vector<MySubType> s;
    foo(s);  // compiler error
}

error: invalid initialization of reference of type std::vector<MyType, std::allocator<MyType> >& from expression of type std::vector<MySubType, std::allocator<MySubType> >

Is there some way to do something like this?

Basically, if there's a way to do this:

std::vector<MySubType> s;
std::vector<MyType>& t = s;

I'd be happy...

+3  A: 

Here's the problem with this - if s and t point to the same object, what's to stop you putting MyOtherSubType (not related to MySubType) into into t? That would make s contain objects that are not MySubType. I don't know of any type-safe programming language that lets you do this. If it were allowed, imagine the problems we'd have:

//MySubType inherits from MyType
//MyOtherSubType also inherits from MyType

std::vector<MySubType> s;
std::vector<MyType>& t = s;

MyOtherSubType o;
t.push_back(o);

Since t and s are exactly the same object under different names, the element at s[0] is something that is not MySubType. Huge problem - we could never be sure that our vectors contained the types they are supposed to! Thus, the compiled disallows it.

Smashery
There are a number that do support this type of covariance, just not C++
1800 INFORMATION
But I don't know of any ;-) Of course, weakly-typed languages like python will let you, but is there any type-safe language that lets you?
Smashery
Java does, but you must specify whether you're doing covariance or contravariance. Effective Java introduces the "PECS" concept: producer extends, consumer super. What that means is this (if you're dealing with type T): if an incoming container is used for getting items out of (container used as producer), you specify this as "Collection<? extends T>"; if it's used for putting items into (container used as consumer), then you specify this as "Collection<? super T>".
Chris Jester-Young
Obviously, if a function needs to take a collection both to get items out of it, and to put items back in, then the type would be invariant ("Collection<T>"). :-P
Chris Jester-Young
C# in .Net 4.0 does also - you have to specify whether you are doing "in" (covariant) or "out" (contravariant)
1800 INFORMATION
http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
1800 INFORMATION
well, that makes sense as to why I can't do it - thanks, but it still doesn't solve what I'm trying to do...
paquetp
@Smashery: Your point is moot because you should not be using std::vector<MyType> at all if you want to store more than that exact concrete type (as the OP clearly does) -- you should be using std::vector<MyType*>.
j_random_hacker
A: 

C++ does not support covariance on template types, any more than C# currently does, much for the same reasons. If you want to do this, it is best to make your vector be templatised on a common interface type (of pointers to that type), and make the function foo accept a vector of that type.

1800 INFORMATION
This kind of thing is done all the time in C++.
Arafangion
All the time? Really? You can assign between templated classes where the template parameters are not the same?
1800 INFORMATION
Needs to be vector of base-class pointers to work. And since STL and raw pointers are ill adviced, smart pointers.
ceretullis
A: 

Unfortunately, there isn't. Different template specialisations are considered different classes; you can't convert between std::vector<MyType> and std::vector<MySubType>.

You could split the "do stuff" common bit of foo() into a separate function and have a separate loop over each vector (or possibly use std::foreach).

Peter
+8  A: 

This might fix your problem

template <typename T>
void foo(std::vector<T>& s)
{
    typename std::vector<T>::iterator i;
    for (i = s.begin(); i != s.end(); i++)
        // do stuff on *i
}
oykuo
Either that or pointers I would think.
sharth
I like this, but had to change it a bit.I put typename before T in template <T>and I also had to put typename before std::vector ... don't know why...not very good with templates
paquetp
The reason you had to put "template<typename T>" is that there are two kinds of template parameters: types and integers, and you have to say which kind is T. You had to put "typename std::vector<T>::iterator i" because the compiler can't tell that "...::iterator" is a type without help--without "typename" it thinks that "iterator" is a data member of vector<T>.
Nathan Kitchen
sorry yes it's right it should be "template <typename T>", I wrote too much C# recently :P
oykuo
Although this will work, it might be better to consider using a vector<MyType*> instead, which would then allow the vector to contain objects of MyType or any of its derived types, but nothing else. (You would then need to manually copy the vector<MySubType*> to a vector<MyType*> to pass to foo(), however.)
j_random_hacker
yeah, I had something like that, but I found myself copying and pasting the loop construct...every time I copy and paste code I think gee - I should probably put this in a common function...but thanks j_random_hacker
paquetp
+2  A: 

Since you say, "Nothing too fancy here", I'm not sure you realize this:

Your original code is broken; or if you're lucky it might not be broken right now, but it's quite fragile and will break as soon as someone does something more than look at the MySubType class.

The problem is that you're passing a MyType* to foo() but it really points to an array of MySubType. If a MySubType object happens to be larger than a MyType object (which is pretty likely if you've added anything to the derived class), then the pointer arithmetic done in the foo() function will be incorrect.

This is one of the serious and classic pitfalls of arrays of derived class objects.

Michael Burr
oh, yeah, your right, sorry - I meant to do this with an array of pointers
paquetp
+1. The OP has corrected the problem with the original pointer-based code, but it remains with the vector-based code -- as a first step, you need to declare the vectors as vector<MyType*> and vector<MySubType*> respectively.
j_random_hacker
A: 

Using boost (for smart pointer):

foo( std::vector<boost::shared_ptr<MyType> >& v )
{
   std::for_each( v.begin(),
                  v.end(),
                  do_something );
}

bar()
{
    std::vector<boost::shared_ptr<MyType> > s;
    // s.push_back( boost::shared_ptr<MyType> ( new MySubType() ) );
    foo( s ); 
}
ceretullis
I don't see how boost solves the original problem here.
Tom
Boost for smart pointers, pointer to base class solves the prob.
ceretullis
+6  A: 

To expand on kuoson's answer, the idiomatic C++ style is to pass iterators to a function rather than containers.

template<typename Iterator>
void foo(const Iterator & begin, const Iterator & end)
{
    Iterator i;
    for (i = begin;  i != end;  ++i)
        // do stuff on *i
}
Mark Ransom
sorry Mark, I liked kuoson's answer better...
paquetp
Nothing wrong with that, thanks for leaving the comment.
Mark Ransom
A: 

If I'd know what's the real code behind those foobars then maybe I'd know better, but isn't there already a solution for your problem in the STL?

for_each(s.begin(), s.end(), DoStuffOnI());

Just put your "do stuff on *i" code in a function or a functor:

struct DoStuffOnI : public std::unary_function<MyType&,void> {
    void operator()(MyType& obj) {
        // do stuff on *i
    }
};

If you're bothered about sending away two parameters instead of one, then ok, maybe you can do something like:

template<typename In>
struct input_sequence_range : public std::pair<In,In> {
    input_sequence_range(In first, In last) : std::pair<In,In>(first, last)
    {
    }
};

template<typename C>
input_sequence_range<typename C::iterator> iseq(C& c)
{
    return input_sequence_range<typename C::iterator>(c.begin(), c.end());
}

template<typename In, typename Pred>
void for_each(input_sequence_range<In> r, Pred p) {
    std::for_each(r.first, r.second, p);
}

Then call for_each like so:

for_each(iseq(s), DoStuffOnI());
wilhelmtell
I like this, but I think the template solution from kuoson is more elegant. Thanks wilhelmtell.
paquetp
+1  A: 

If you want to store objects of multiple different MyType-derived types inside a single vector (as I suspect you do, although that's not necessary for this particular example), you'll need to use a std::vector<MyType*> instead of std::vector<MyType>. This suggestion is analogous to that proposed by Michael Burr for your original pointer code.

This does have the unfortunate side-effect that you cannot implicitly convert a std::vector<MySubType*> to a std::vector<MyType*> to call foo() with. But the conversion code is not too onerous:

void foo(std::vector<MyType*>& s)
{
    ...
}

void bar()
{
    std::vector<MySubType*> s;

    // Populate s
    ...

    std::vector<MyType*> u(s.begin(), s.end());    // Convert
    foo(u);
}

Or, just have bar() use a std::vector<MyType*> from the beginning.

j_random_hacker
thanks j_random_hacker...this is a decent solution, but I prefer the template function better...I also failed to mention that I'm not actually doing this on a std::vector, but on something like a std::vector. The items in the special container cannot be pointers by design (they all must at least be a specific type, which is not a pointer). I didn't explain this in my original post because it's too much typing to explain. :(
paquetp
No problem :) If the elements are stored as objects rather than pointers to them, then *every* function that you want to operate on a variety of types (e.g. both MyType and MySubType) will need to be made into a function template.
j_random_hacker