Search code examples
c++design-patternsobject-slicing

How to hide param type of template base class


Not sure if this is even possible but here goes...

I'm trying to hide the param of a template base class by doing the following:

  • Expose a common interface
  • Implement template base class that implements the common interface
  • Implement several concrete derived classes
  • Use a factory class to instantiate the concrete derived classes

So far so good, but the problem is the return type of my factory class is IBase so the default implementation of foo is called instead of DerivedA or DerivedB :(

Anyway for this approach to work? or back to the drawing board?

// Common interface
class IBase {
public:
  virtual std::string foo() { return "IBase"; };
}

// Template base class
template <typename T>
class Base : public IBase {
public:
  Base(T value) : m_precious(value) {}
  virtual ~Base() {}
protected:
  T m_precious;
}

// Concrete derived classes
class DerivedA : public Base<int> {
public:
  DerivedA(int value) : Base<int>(value) {}

  virtual std::string foo() override {
   return "DerivedA";
  };
}

class DerivedB : public Base<float> {
public:
  DerivedB(float value) : Base<float>(value) {}

  virtual std::string foo() override {
   return "DerivedB";
  };
}

// Factory interface
class Factory {
public:
  template<typename T>
  static IBase create(T value);
};

template<>
IBase Factory::create<int>(int value) {
  return DerivedA(value);
}

template<>
IBase Factory::create<float>(float value) {
  return DerivedB(value);
}

// Caller
int main() {
  int   valueA = 3;
  float valueB = 3.14;

  // This is how I want to use the API
  IBase A = Factory::create(valueA);
  IBase B = Factory::create(valueB);

  std::cout << A.foo() << std::endl;
  std::cout << B.foo() << std::endl;
}

The above code prints:

IBase
IBase

But I want this:

DerivedA
DerivedB

Solution

  • You currently have object slicing, your code should be something like:

    // Factory interface
    class Factory {
    public:
      template<typename T>
      static std::unique_ptr<IBase> create(T value);
    };
    
    template<>
    std::unique_ptr<IBase> Factory::create<int>(int value) {
      return std::make_unique<DerivedA>(value);
    }
    
    template<>
    std::unique_ptr<IBase> Factory::create<float>(float value) {
      return std::make_unique<DerivedB>(value);
    }
    

    With usage:

    auto A = Factory::create(valueA);
    auto B = Factory::create(valueB);
    
    std::cout << A->foo() << std::endl;
    std::cout << B->foo() << std::endl;