7

I have the following code that doesn't compile.

class Base {
    public:
        virtual ~Base() { };
};

class Derived : public Base { };

class NotDerived { };

template <typename T>
class Group { };

int main() {
    Group<Base> *g = NULL;

    g = new Group<Base>();       // Works
    g = new Group<Derived>();    // Error, but I want it to work

    g = new Group<NotDerived>(); // Error, as expected
}

I understand that this won't compile because g is a different type than Group<Derived>. To make this work in Java I would do something such as Group<? extends Base> g, but C++ doesn't have that keyword as far as I know. What can be done?

Edit: I would like to clarify that I do not want it possible to set types not derived from Base as g. I have updated my example to explain this.

Edit 2: There are two solutions to my problem. Dave's I found to be simple and easy to define. But Bowie's (along with Mark's additions) suited my needs better.

Community
  • 1
  • 1
Ryan
  • 73
  • 1
  • 4
  • I'm not familiar with how this sort of thing works in Java. What are you trying to say with that syntax? – Nicol Bolas Aug 25 '11 at 01:38
  • This wouldn't work in Java either. The syntax of ` extends ...>` is not for supporting covariance, but is there to add a constraint on what types the generic may use. – Mark H Aug 25 '11 at 01:46
  • See [C++ template polymorphism](http://stackoverflow.com/questions/2203388/c-templates-polymorphism) – Praetorian Aug 25 '11 at 01:47
  • Nicol: It's so that I can have a type of factory class that returns a certain type of interface. Mark: it certainly does work in Java. Praetorian: That appears to be exactly my problem, Thanks! – Ryan Aug 25 '11 at 01:55

5 Answers5

5

The classes Group<Base> and Group<Derived> are entirely unrelated, different classes. Pointers to them are not convertible in either direction.

If you need runtime-polymorphic behaviour, your class template Group could derive from a common (non-templated) base class:

class Group // base
{
   virtual ~Group() { }
};

template <typename T>
class ConcreteGroup : public Group
{
  // ...
  T * m_impl;
};

Group * g1 = new ConcreteGroup<A>;
Group * g1 = new ConcreteGroup<B>;
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
3

You could make Group< Base > a base class of all Group< T > T != Base.

class Base {
    public:
        virtual ~Base() { };
};

class Derived : public Base { };


template <typename T> class Group;

struct Empty { };

template <typename T>
struct base_for_group_t {
    typedef Group<Base> type;
};

template <>
struct base_for_group_t<Base> {
    typedef Empty type;
};

template <typename T>
class Group : public base_for_group_t<T>::type { };

int main() {
    Group<Base> *g = 0;

    g = new Group<Base>();    // Works
    g = new Group<Derived>(); //  now works
}
Bowie Owens
  • 2,798
  • 23
  • 20
  • Unfortunately now something like this is legal: `class NotDerived {}; g = new Group();`. I would like to maintain type safety if possible. – Ryan Aug 25 '11 at 02:14
2

Bowie Owens's Answer handles the covariance you need to solve the original question. As for the constraint you asked for in the edited question - you can achieve this by using type traits.

template <typename T, class Enable = void> class Group;

template <typename T>
class Group<T, typename enable_if<is_base_of<Base, T>::value>::type> 
    : public base_for_group_t<T>::type { };
Community
  • 1
  • 1
Mark H
  • 13,797
  • 4
  • 31
  • 45
  • Unfortunately, I cannot get it to compile on my end (I am unfamiliar with how to correctly use type traits). Can you provide a more complete example? (I assumed `Group` is supposed to be `template class Group`, however that didn't work. – Ryan Aug 25 '11 at 03:20
  • Try again with the `::value` on is_base_of. (Difference required between `boost::enable_if` and `std::enable_if`). If not, what's the error you get? – Mark H Aug 25 '11 at 03:29
  • Ah, I did not include the std namespace. Your original code works as expected. ;) – Ryan Aug 25 '11 at 03:42
  • You should accept Bowie's answer rather than mine, he done the clever work. – Mark H Aug 25 '11 at 03:52
1

I don't think C++ support that. C++ Template is handled completely at compile time so it doesn't support polymorphism. That means the type of the template arguments should be exactly the same for both side of the assignment expression.

Mu Qiao
  • 6,941
  • 1
  • 31
  • 34
0

I think I understand what you're going for. I'm not sure it's the best approach (and you might want to look at Boost.Factory).

template <class T>
class Factory {

public:
  virtual T* operator()() = 0;
};

template <class Derived, class Base>
class ConcreteFactory : public Factory<Base> {

public:
  virtual Base* operator()() {
    return new Derived();
  }
};

class Base {
public:
  virtual ~Base() {};
};

class Derived1 : public Base { };
class Derived2: public Base {};

class NotDerived {};

int main()
{
  Factory<Base>* g;

  g = new ConcreteFactory<Derived1, Base>;
  g = new ConcreteFactory<Derived2, Base>;
  //  g = new ConcreteFactory<NotDerived, Base>; // Will not work if you try to do this
}
Dave S
  • 20,507
  • 3
  • 48
  • 68
  • This appears to be the most type-safe solution so far. The definition of both Derived1 and Base is a bit redundant, but it's nothing that I wont know already (and I can avoid with a typedef). Thanks, :) – Ryan Aug 25 '11 at 03:22