-1

Depending on some argument, I need to use one of two derived classes. I understand that I can declare the base class and use it to initialize one of the derived classed. How do I do that when the base class is a base class template?

Here is a simplified scenario:

template <class T>
class shape
{
}

class rectangleInt : public shape<int> 
{
}

class rectangleDouble : public shape<double> 
{
}

int main() {

    // I want to do the following
    shape<T> shape;
    if (args == int) {
        shape = rectangleInt();
    } else {
        shape = rectangleDouble();
    }


}

What do I have to change in my code to accomplish what I want?

John Wong
  • 31
  • 9
  • *" I understand that I can declare the base class and use it to initialize one of the derived classed."* -- This reads wrong. I do not know if it is a misunderstanding on your part or a poor choice of words. My guess is that you meant (or misinterpreted) something like a base class pointer can point to a derived class. – JaMiT Jul 14 '23 at 06:43
  • 1
    `rectangleInt` and `rectangleDouble` do not share a common base class as `shape` and `shape` are distinct classes. – Richard Critten Jul 14 '23 at 06:46
  • *"How do I do that when the base class is a base class template?"* -- A better way to describe what is in your example code is "the base classes are different instantiations of the same class template". Which is probably the source of your confusion. Let me see if I can find a suitable question about this. – JaMiT Jul 14 '23 at 06:48
  • 1
    You need to be a bit more clear do you know the type at compile time then you don't even need to build something complicated just write the type you want in code. If you Do need to select an instance at runtime, then your shape class itself must be derived from an abstract baseclass say `shape_itf` and you need a factory method that returns a `std::unique_ptr` – Pepijn Kramer Jul 14 '23 at 06:58
  • I did not find a true duplicate (partly because I am not clear on what you intended to ask), but you might find [Is it legal for class template specialisations to inherit from different base classes?](https://stackoverflow.com/q/51504334) illumintaing. If you intended `shape` to be a pointer, see [A pointer to abstract template base class?](https://stackoverflow.com/q/1497552) *Key statement from the answer to the former: "Each template instantiation is a separate class of its own."* – JaMiT Jul 14 '23 at 07:01

1 Answers1

1

Unfortunately shape<int> and shape<double> are distinct, separate classes and therefore one cannot automatically use them interchangeably.

So you can either make another unique base class for all the templates:

class abstract_shape { };

template<class T>
class shape : public abstract_shape { };

With this design you can actually store pointers all classes derived from any shape<T> in a variable of type abstract_shape*.

abstract_shape* my_shape1 = &my_double_rect; 
abstract_shape* my_shape2 = &my_int_rect;

If this does not solve the problem or does not fit the design, one can opt for a std::variant based solution:

using shape_ptr = std::variant<std::nullptr_t, shape<int>*, shape<double>*>;

shape_ptr my_shape1 = &my_double_rect;
shape_ptr my_shape2 = &my_int_rect;

A third option is to store the shapes derived from shape<int> and shape<double> separately, and then templatize the functions which uses them:

std::vector<std::unique_ptr<shape<int>>> my_int_shapes;
std::vector<std::unique_ptr<shape<double>>> my_double_shapes;

template<class T>
auto do_something_with_shape(shape<T>* obj);

auto do_something_with_all_shapes() {
    for (auto& int_shape : my_int_shapes) {
        do_something_with_shape(int_shape.get());
    }

    for (auto& double_shape : my_double_shapes) {
        do_something_with_shape(double_shape.get());
    }
}
Mestkon
  • 3,532
  • 7
  • 18
  • Thanks. The `std::variant` solution is quite elegant for my situation. I used `shape_ptr = std::variant>, std::unique_ptr>>;` I just have a bit of a problem because I didn't provide the full details. The class `shape` has a struct called `Foo` and a function `Foo bar()`. I got this error. `error: base operand of '->' has non-pointer type 'shape_ptr' `for the this line in my code `while ((foo = shape_ptr->bar())) {` I am quite confused. `shape_ptr` is a pointer but the error says it is a non-pointer type. – John Wong Jul 15 '23 at 08:16
  • The `shape_ptr` in `while ((foo = shape_ptr->bar())) {` is name of the pointer. Made a typo. – John Wong Jul 15 '23 at 17:51
  • I solved it. For posterity in case anyone needs it in the future, you can access the function like this `std::get>>(indexlr)->bar()` – John Wong Jul 15 '23 at 21:43
  • 1
    If you do not know whether the `shape_ptr` contains a `shape` or a `shape` then you might want to use `std::visit`. `std::visit([](auto& ptr) { return ptr->bar(); }, my_shape_ptr);` – Mestkon Jul 17 '23 at 09:39
  • That solution is quite clean, but I am encountering a static assertion problem because the return types are different. `error: static assertion failed: std::visit requires the visitor to have the same return type for all alternatives of a variant`. The return types are of `shape::Foo` and `shape::Foo`. `Foo` is the same across the board, but just within the class. Is there a way to make the return type the same but keep `Foo` within the `shape` class template? – John Wong Jul 18 '23 at 16:47
  • 1
    If `shape::Foo` is identical for all valid `T` then I would suggest separating it from the `shape` template. For example declare it above `shape` as `shapeFoo` or something. If this is not possible, one alternative is to force the visitor to also return a variant `using shapeFoo = std::variant::Foo, shape::Foo>;` `std::visit([](auto& ptr) -> shapeFoo { return ptr->bar(); }, my_shape_ptr);` – Mestkon Jul 18 '23 at 16:58
  • Yeah forcing the visitor to return a variant worked but I had other complications. In the end, I moved the `Foo` out of `Shape` which honestly was the simplest solution but I wanted to avoid changing the class as much as possible. Thanks again for everything. – John Wong Jul 19 '23 at 14:21