c++ – Arrays of template class objects


I would like an array of pointers to instances of a template class. I have investigated different solutions on the site and found problems (for my application) in all of them. I now have something that “works” but I’d like some criticism of my solution.

The template parameter is infinitely variable, e.g., I cannot enumerate every specialization of this template class. The template class T can be any POD, array of POD, or struct of POD.

The complete set of T is known at compile time. Basically, I have a file which defines all the different T used to instantiate the objects, and use Xmacros (https://en.wikipedia.org/wiki/X_Macro) to create the array of objects.

I know this isn’t a great idea. Let’s gloss over that for the time being. This ends up being more a curiosity.

These are the things I’ve looked into.

Create base and derived classes

class Base {
  virtual void SomeMethod() = 0;
}

template <class T>
class Derived : Base {
  void SomeMethod() {...}
}

The problem with this is I cannot declare all the virtual methods in Base that I want to overload, as virtual methods cannot be templated. Otherwise, it would be a perfect solution.

std::any/std::variant

I am using C++17, so I could define the virtual base methods taking std::any. But it cannot hold arrays, which precludes its use here.

CRTP

It seems this would not help me create an array of these different objects. I would need to do something like

template <typename D, typename T>
class Base
{
    ...
};

template <typename T>
class Derived : public Base<Derived, T>
{
    ...
};

So I still end up with trying to create an array of Derived<T> objects.

Visitor Pattern

Again it looks like I would need to enumerate every possible type the Visitable class needs to service, which, while not impossible (again, I have a file which defines all the different T that will be used) seems like more Xmacros, which is just making the problem more complicated.

This is what I came up with. It will run in https://www.onlinegdb.com/online_c++_compiler

#include <iostream>
#include <array>
#include <typeinfo>

// Base class which declares "overloaded" methods without implementation
class Base {
 public:
  template <class T>
  void Set(T inval);
  template <class T>
  void Get(T* retval);
  virtual void Print() = 0;
};

// Template class which implements the overloaded methods
template <class T>
class Derived : public Base {
 public:
  void Set(T inval) {
    storage = inval;
  }
  void Get(T* retval) {
    *retval = storage;
  }
  void Print() {
    std::cout << "This variable is type " << typeid(T).name() <<
      ", value: " << storage << std::endl;
  }
 private:
  T storage = {};
};

// Manually pointing base overloads to template methods
template <class T> void Base::Set(T inval) {
  static_cast<Derived<T>*>(this)->Set(inval);
}
template <class T> void Base::Get(T* retval) {
  std::cout << "CALLED THROUGH BASE!" << std::endl;
  static_cast<Derived<T>*>(this)->Get(retval);
}

int main()
{
  // Two new objects
  Derived<int>* ptr_int = new Derived<int>();
  Derived<double>* ptr_dbl = new Derived<double>();
  
  // Base pointer array
  std::array<Base*, 2> ptr_arr;
  ptr_arr(0) = ptr_int;
  ptr_arr(1) = ptr_dbl;

  // Load values into objects through calls to Base methods
  ptr_arr(0)->Set(3);
  ptr_arr(1)->Set(3.14);

  // Call true virtual Print() method
  for (auto& ptr : ptr_arr) ptr->Print();

  // Read out the values
  int var_int;
  double var_dbl;
  std::cout << "First calling Get() method through true pointer." << std::endl;
  ptr_int->Get(&var_int);
  ptr_dbl->Get(&var_dbl);
  std::cout << "Direct values: " << var_int << ", " << var_dbl << std::endl;
  std::cout << "Now calling Get() method through base pointer." << std::endl;
  ptr_arr(0)->Get(&var_int);
  ptr_arr(1)->Get(&var_dbl);
  std::cout << "Base values: " << var_int << ", " << var_dbl << std::endl;

  return 0;
}

When this is run, it shows that calling the methods on Base correctly point to the Derived implementations.

This variable is type i, value: 3                                                                                                    
This variable is type d, value: 3.14                                                                                                 
First calling Get() method through true pointer.                                                                                     
Direct values: 3, 3.14                                                                                                               
Now calling Get() method through base pointer.                                                                                       
CALLED THROUGH BASE!                                                                                                                 
CALLED THROUGH BASE!                                                                                                                 
Base values: 3, 3.14  

Essentially I am manually creating the virtual method pointers. But, since I am explicitly doing so, I am allowed to use template methods in Base which point to the methods in Derived. It is more prone to error, as for example for each template method I need to type the method name twice, i.e., I could mess up:

template <class T> void Base::BLAH_SOMETHING(T inval) {
  static_cast<Derived<T>*>(this)->WHOOPS_WRONG_CALL(inval);
}

So after all this, is this a terrible idea? To me it seems to achieve my objective of circumventing the limitation of templated virtual methods. Is there something really wrong with this? I understand there could be ways to structure the code that make all this unnecessary, I am just focusing on this specific construction.