tags:

views:

185

answers:

5

I have a template class that will bundle some information with a type:

template <typename T>
class X
{
  int data[10]; // doesn't matter what's here really
  T t;
public:
  // also not terribly relevant
};

Then lets say we have a Base class and Derived class:

class Base {};

class Derived : public Base {};

I'd like to be able to do something like this (but don't think I can):

void f(shared_ptr<X<Base> > b);

shared_ptr<X<Derived> > d(new X<Derived>);

f(d);

Is there a way that I can make pointers to X<T> convertible to pointers of X<Y> if T* is convertible to Y*?

A: 

EDIT: What you are trying to do is possible; you can use the convertibility test and the static assertion provided by the boost libraries to generate a compile-time error in case of type incompatibility.
Here is an example:

#include <string> 
#include <iostream> 
#include <boost/type_traits/is_convertible.hpp>
#include <boost/static_assert.hpp>

template<typename MyType>
class MyTypeInfo{
public:
 MyType* myType;

 MyTypeInfo(MyType* _myType){
  myType = _myType;
 }

 template <typename DestinationType>
 operator MyTypeInfo<DestinationType>() {
  BOOST_STATIC_ASSERT((boost::is_convertible<MyType,DestinationType>::value));
  MyTypeInfo<DestinationType> ret((DestinationType*)myType);
  return ret;
 }

};

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

class NonDerived{
};

int _tmain(int argc, _TCHAR* argv[])
{
 // this one is ok
 MyTypeInfo<Derived> d(new Derived());
 MyTypeInfo<Base> b = (MyTypeInfo<Base>)d;
 b.myType->do_something();

 // this one fails to compile
 //MyTypeInfo<NonDerived> nd(new NonDerived());
 //MyTypeInfo<Base> b2 = (MyTypeInfo<Base>)nd;
 //b2.myType->do_something();

 return 0;
}

If you don't want or cannot use the boost libraries, you can refer to Andrei Alexandrescu's wonderful book "Modern C++ Design" for a possible implementation of the convertibility test and the static assertion macro.

Paolo Tedesco
As you show this will make X<Derived> convertible to X<Base>, but it will not make X<Derived>* convertible to X<Base>*
coombez
It also has the small problem of slicing. In this simple example it is not obvious but the member t will be sliced and loose all information specific to Derived (via the assignment operator)
Martin York
I don't think this solves my problem either. I can make my own pointer types that work with my type, but that's a way around solving my problem rather than solving it. I can also create these types without relying on reinterpret_casting as you have, and therefore do it much more safely.
coombez
A: 

G'day,

Have you thought about why you want to do this?

Just coming back to the basics that inheritance is for specialising behaviour for different types of derived objects, e.g. the draw() function declared in a base class called Shape would produce different output for a derived class Square compared to a derived class Circle.

Templates should be used when you want the same sort of behaviour irrespective of the type, e.g. the pop function in a stack of integers behaves the same way as the pop function in a stack of floats behaves the same way as the pop function in a stack of objects of type Foo.

HTH

cheers,

Rob Wells
I would like to keep that polymorphic behaviour, and also tack on unrelated functionality. An interesting case might be shared_ptr<optional<Base> >, the info I'd like to tack on is different, but the problem is the same. If I have a shared_ptr<optional<Derived> > maybe I'd like to pass it into a function that takes a shared_ptr<optional<Base> > (admittedly I can't see a use for this with the optional).
coombez
@coombez, hmmm. can you create a template instance specialised for a pointer to Base class then surely you can then use that for pointers of instances of your Derived class?
Rob Wells
+2  A: 

Rephrasing the question to a more general: is it possible to convert pointers from unrelated types if the unrelated types are themselves convertible?, the answer is not. And different template instantiations define different unrelated types.

You can implicitly convert (see note below) a pointer to a derived object to a pointer of the base object (some restrictions apply due to access or ambiguity) or you can make an explicit conversion from a base pointer to a derived pointer (again with some restrictions). You can also convert back and forth to void*, but converting to an unrelated type ends up with undefined behavior.

There are clear reasons for pointers not being convertible from unrelated types. The first of which is that converting one pointer type to another pointer type will not convert the pointed memory. That is, you can convert an int to a double, but converting a pointer to an int to a pointer to double would make the compiler believe that there are 8 bytes of memory at the pointed address while in fact only 4 bytes are present (assuming 32 or 64 bit architecture with 32 bit integers and 64 bit doubles). Dereferencing the pointer will surely end bad.

Notes from the standard:

Section 4.10 of the standard deals with pointer conversions, first paragraph deals with null-pointer conversions, second paragraph with void pointer conversions. The third paragraph states that you can convert a pointer to type D to a pointer of type B as long as B is a base class of D. No other pointer conversion is defined there. In Section 5.4 where explicit conversions are specified, paragraph 7 deals with pointer conversions and only adds the possible explicit conversion from B* to D* in several situations as long as B is a base of B.

David Rodríguez - dribeas
A: 

While rethinking on the question I just realized of another problem you have. Polymorphic behavior can only be achieved through pointers and/or references. Trying to do so with values produces what is called slicing, where the original derived object is converted (copied) into a base object loosing all the extra information.

The common example of slicing is this one:

class Base 
{
public:
   virtual void polymorphic() { 
      std::cout << "Base" << std::endl;
   }
};
class Derived : public Base
{
public:
   virtual void polymorphic() {
      std::cout << "Derived" << std::endl;
   }
};
void f( Base b ) {
   b.polymorphic();
}
int main() {
   Derived d;
   f( d ); // prints Base
}

Now, the original question deals with conversion of pointer types and I answered before. I believe that your code presents a variant of the slicing problem. We can play a little with the undefined behavior of forcing pointer conversions from unrelated types:

// same Base and Derived definitions as above
template <typename T>
struct Holder
{
   T data;
};
int main() {
   Holder<Derived> derived;
   Holder<Derived>* derived_ptr = &derived;
   Holder<Base>* base_ptr = reinterpret_cast<Holder<Base>* >( derived_ptr ); // undefined!!!
   std::cout << "derived_ptr=" << derived_ptr << ", base_ptr=" << base_ptr << std::endl;
   base_ptr->data.polymorphic(); // prints Base
}

First of all, this is undefined behavior according to the standard... but bear with me. We can try to work what a common compiler could implement. We have an object of type Holder, and a pointer to it (*derived_ptr*), now we reinterpret the pointer as a pointer to Holder. At this point *base_ptr* and *derived_ptr* have the same value, they point to the same address (check the output).

In the last step we access the data field of the Holder stored at &derived, and we call the virtual function * polymorphic()* on it... but in Holder, the data field is not a reference or pointer, it is an element of type Base, so the compiler can decide that it will call the Base version of it, and it so does in g++ 4.0.

This is all undefined behavior, so try it with your compiler of choice, it may end up with the same or different outcomes.

If you change the template definition to store a pointer, then you get the expected polymorphic behavior. That is, expected in this case, knowing the memory footprint of the objects at hand. Not that you can expect anything from the unexpected undefined behavior.

David Rodríguez - dribeas
I know I can do what I want safely with reinterpret casts on pretty much every system. That is how I would implement the pointer conversions if I -could-. I figured I couldn't and everything both your posts say is my understanding also.
coombez
Actually I don't think I can for classes with multiple inheritance.
coombez
If the template, as in your example, contains an attribute of type T (not reference, nor pointer) then either it is not safe or your definition of safe is 'works now and if I am really careful in the future'. There are many things that can go wrong, 'template <typename T> struct X { int x; T y; int z; }'. If you create an instance of Derived type and Derived has more attributes than Base, then accessing z through a reinterpret_cast<> to Base* from a Derived* will access Derived data instead of 'z'...
David Rodríguez - dribeas
... as the compiler will consider that the 'y' attribute just takes as much memory as Base, while Derived is larger (it cannot be smaller). You can force the 'y' field to the end of the template, but you (an other possible developers) must remember to never change the order or add fields at the end. And that is just one of the things that will go wrong with all systems I know of.
David Rodríguez - dribeas
While the first answer dealt with the impossibility of doing it (what you asked) this dealt with the specific problem of expecting to use polymorphism with attributes that are not references/pointers.
David Rodríguez - dribeas
A: 

I don't want to point out the obvious but is there any reason you can't make the function a template ? I.e.

template <typename T>
void f(shared_ptr<T> b);

shared_ptr<X<Derived> > d(new X<Derived>);

f(d);
Tjerk Santegoeds