How can I offload dependency injected template class providing templated functions to pimpl class

86 Views Asked by At

I have an application class that can take in a dependent class as a template argument to the constructor. This dependent class is required to provide certain templated functions that the application class can call. I would like to offload this dependent class object to a pimpl class so the application class is not a template class and thus header-only.

Here is a rough idea of what I mean.

///////////
// impl.h
///////////

template<typename Helper>
struct Impl
{
public:
    Impl(Helper& helper) : helper_(helper)
    {
    }

    template <typename T>
    void someHelperFn1(T t)
    {
        helper_->fn1(t);
    }
 
    template <typename U>
    SomeOtherClass<U> someHelperFn2()
    {
        return helper_->fn2();
    }


private:
    Helper& helper_;
};

///////////
// app.h
///////////

#include "impl.h"

class App
{
public:
  template<typename Helper>
  App(Helper &h) :impl_(new Impl) {}

  template <typename T>
  void someHelperFn1(T t)
  {
     impl_->someHelperFn1(t);
  }
 
  template <typename U>
  SomeOtherClass<U> someHelperFn2()
  {
     return impl_->someHelperFn2();
  }

  void someAppFn();

private;
  std::unique_ptr<Impl> impl_;
};

///////////
// app.cpp
///////////

void App::someAppFn()
{
  // some useful code
}

I realize the above code doesn't compile since Impl is really a template class and so App would also be a template class too. That is what I would like to avoid so that App is not a header-only class. I found something similar except the functions that I want to call from the helper dependency are template functions and they are not in this case. It seemed pretty close otherwise to what I wanted to do.

Any ideas on how I can avoid making App a template class?

I tried making the helper class use a common base class but that is not really possible with the template functions.

Also, note that I am limited to C++ 17 for the compiler.

1

There are 1 best solutions below

3
Pepijn Kramer On

You will need to make sure the public header file (the one with the class that has the pimpl pointer) doesn't expose the header file only class template of the implementation. Use an interface for that like this. I did not dependency inject the implementation because that should not be needed.

#include <memory>
#include <iostream>

// public header file

// for pimpl pattern I often use an interface
// (also useful for unit testing later)
class PublicItf
{
public:
    virtual void do_something() = 0;
    virtual ~PublicItf() = default;

protected:
    PublicItf() = default;
};

// the public class implements this interface
// and the pimpl pointer points to the same interface
// added advantage you will have compile time checking that
// the impl class will all the methods too.
class PublicClass final : 
    public PublicItf
{
public:
    PublicClass();
    virtual ~PublicClass() = default;
    void do_something() override;

private:
    std::unique_ptr<PublicItf> m_pimpl; // the interface decouples from the template implementation (header file only) 
};

// private header file
// this can now be a template
template<typename type_t>
class ImplClass final :
    public PublicItf
{
public:
    void do_something() override
    {
        m_value++;
        std::cout << m_value << "\n";
    }



private:
    type_t m_value{};
};

// C++ file for Public class
// inlcude public header and impl header (template)

PublicClass::PublicClass() :
    m_pimpl{ std::make_unique<ImplClass<int>>() }
{
};

void PublicClass::do_something()
{
    m_pimpl->do_something();
}

// main C++ file

int main()
{
    PublicClass obj;
    obj.do_something();

    return 0;
}