Search code examples
c++templates

How to pass c++ template class to constructor when template arguments are not known?


Sorry if this is a double post. I'm almost sure this has been asked multiple times, but I'm unable to find it.

I mainly have a Java background. I haven't used C++ for a long time but I currently need to use it for microcontroller programming. In my main method, I have a display object for drawing to an e-paper display. I'd like to pass this object to a separate class that performs some drawing operations with it, but I'm struggling a little with this.

The display class is templated and gets initialized in the main method.

GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(SS, 17, 16, 4));

Now, my drawing class "Graph" has a constructor where display is passed as a parameter:

class Graph {

    public:
        Graph(GxEPD2_BW *display);
        ...

That's not working as C++ is complaining about the missing argument list of the templated class GxEPD2_BW.

The problem now is, I don't know what arguments to provide here. The template arguments GxEPD2_154_D67 and GxEPD2_154_D67::HEIGHT define the specific display in use and I don't want my drawing class to rely on a specific display. It's supposed to take any kind of display class, no matter the parameterization, and work with it.

In Java, I would build the constructor like this:

public class Graph {

    public Graph(GxEPD2_BW<?, ?> display) {
        ...

and henceforth, I could pass any display implementation to it without worrying about the underlying display characteristics.

How would I go about this in C++? Or am I forcing something upon C++ that it's not supposed to do?


Solution

  • For the sake of a simpler example consider this template and a class that expects an instance as parameter to the constructor:

    template <typename T>
    struct Foo {};
    
    struct Bar {
        Bar(Foo<??> ) {}    // pseduo code. Doesn't work like this!
    };
    

    First you need to understand that C++ templates are nothing like Java generics. Java generics are based on type erasure. Thats what allows you to have a GxEPD2_BW<?, ?> display. C++ templates are not based on type erasure. Foo<int> and Foo<double> are two completely different types. They have nothing in common (other than being instantiations of the same class template). C++ templates can be used for type erasure, but they don't do it out of the box. In C++ there is no type Foo<?> that would be the equivalent to the generic in Java.

    Basically you have two choices. You can introduce a common base class, such that Foo<int> and Foo<double> do have a common base:

    struct Base {};
    
    template <typename T>
    struct Foo : Base {};
    
    struct Bar {
        Bar(std::unique_ptr<Base> b) {}
    };
    

    For runtime polymorphism you need pointers or references.

    Alternatively you can consider to make Bar itself a template:

    template <typename T>
    struct Foo {};
    
    template <typename T>
    struct Bar {
        Bar(Foo<T> f) {}
    };
    

    Here, again, Bar<int> and Bar<double> are two completely different types, that have nothing in common (other than being instantiations of the same class template). And each has a concrete type (not a template) as argument for the constructor.

    There are more variants and different ways. For example you could have a non-template Bar and only its constructor is a template. However, the two above are what I would consider the most straightforward ways.