2

Is it possible to implement a class template in such a way that one object could be casted to another if their template arguments are related? Here is an exaple to show the idea (of course it will not compile):

struct Base {};
struct Derived : Base {};

template <typename T> class Foo {
    virtual ~Foo() {}
    virtual T* some_function() = 0;
};

Foo<Derived>* derived = ...;
Foo<Base>* base = derived;

The additional problem here is that Foo is an abstract class used as an interface containing functions returning T& and T*, so I can't implement a template copy constructor.

I'm writing a universal Iterator class which can hold any STL iterator, and in addition to type erasure I'd like it to be polymorphic, i.e. I could write something like this:

std::list<Derived> l;
MyIterator<Base> it(l.begin());

UPD: That was my mistake, I didn't actually need casting Foo* to Foo* to implement MyIterator, so I think the question is not actual anymore.

lizarisk
  • 33
  • 1
  • 4

3 Answers3

4

The template argument has nothing to do with the content of the object you are pointing to. There is no reason this should work. To illustrate

struct Base { };
struct Derived : Base {};

template<typename T> struct A { int foo; };
template<> struct A<Base> { int foo; int bar; };

A<Derived> a;
A<Base> *b = &a; // assume this would work
b->bar = 0; // oops!

You will eventually access integer bar that doesn't really exist in a!


OK, now that you provided some more information, it's clear you want to do something completely different. Here is some starter:

template<typename T>
struct MyIterator : std::iterator<...> {
  MyIterator():ibase() { }
  template<typename U>
  MyIterator(U u):ibase(new Impl<U>(u)) { }
  MyIterator(MyIterator const& a):ibase(a.ibase->clone())

  MyIterator &operator=(MyIterator m) {
    m.ibase.swap(ibase);
    return *this;
  }

  MyIterator &operator++() { ibase->inc(); return *this; }
  MyIterator &operator--() { ibase->dec(); return *this; }
  T &operator*() { return ibase->deref(); }
  // ...

private:
  struct IBase { 
    virtual ~IBase() { }
    virtual T &deref() = 0; 
    virtual void inc() = 0;
    virtual void dec() = 0;
    // ...

    virtual IBase *clone() = 0;
  };
  template<typename U>
  struct Impl : IBase { 
    Impl(U u):u(u) { }
    virtual T &deref() { return *u; }
    virtual void inc() { ++u; }
    virtual void dec() { --u; }
    virtual IBase *clone() { return new Impl(*this); }
    U u;
  };

  boost::scoped_ptr<IBase> ibase;
};

Then you can use it as

MyIterator<Base> it(l.begin());
++it; 
Base &b = *it;

You may want to look into any_iterator. With a bit of luck, you can use that template for your purpose (I haven't tested it).

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Oh, so simple! I've written almost exactly the same code, but with IBase and Impl as global classes (not nested) - this was I mistake that made me stuck, because Impl was inheriting from Ibase::value_type>, which was different from T, so the pointers were not covariant. Now I've put them inside MyIterator and everything works :) – lizarisk Feb 06 '11 at 00:16
  • Any ideas on how to implement `MyIterator`'s `operator==` for cases like when one is `MyIterator::Impl::const_iterator>` and the other is `MyIterator::Impl::iterator>`? – aschepler Feb 06 '11 at 18:05
  • @aschepler I think the any_iterator documentation is correct: Either you make it safe (by only allowing to compare the same impls, checked using typeid), in which case it's impossible to implement it. Or (possibly only in case you regognized the type-ids to be different) you static_cast one side to the other, in which case you have to assume both iterators have the same underlying layout and would "work" when interpreted as a different type. – Johannes Schaub - litb Feb 06 '11 at 19:12
  • Of course, the latter idea makes the entire thing type-unsafe, so I wouldn't go that way. – Johannes Schaub - litb Feb 06 '11 at 19:18
4

While the other answers have pointed that this kind of relationship isn't "built-in" with templates, you should note that it is possible to build functionality to work with this sort of relationship. For example, boost::shared_dynamic_cast and friends given

class A { ... };
class B : public A { ... };

let you cast between boost::shared_ptr<A> and boost::shared_ptr<B>.

Note that if you do go about implementing something like this, you'll have to be careful about the operations that MyIterator supports. For example, using your example of MyIterator<Base>(std::list<Derived>::iterator), you should not have an lvalue version of operator*(), eg,

  *myIter = someBaseValue;

should not compile.

Logan Capaldo
  • 39,555
  • 5
  • 63
  • 78
  • My iterator example starter-implementation returns lvalues from the dereference operator. I think that's a sensible thing to do. – Johannes Schaub - litb Feb 05 '11 at 21:28
  • The prototypical iterators (pointers) allow sliced assignments (if the base class has public assignment). I think it's reasonable for the class template to support it and put responsibility to be careful with it onto the programmer. – aschepler Feb 05 '11 at 22:55
  • Shrug. Maybe the better argument is "avoid mixing inheritance and assignment" instead of preventing it with the iterator class. This example isn't really slicing though, you're assigning a base to a derived, not a derived to a base. – Logan Capaldo Feb 06 '11 at 00:13
1

Foo<Derived> doesn't inherit Foo<Base>, so you can't convert the former to the latter. Also, your assumption is wrong: dynamic_cast will fail.

You could create a new object of instance of Foo<Base> that copies your Foo<Derived> instance, but I guess this is now what you're looking for.

peoro
  • 25,562
  • 20
  • 98
  • 150