Search code examples
c++c++14variadic-templates

Replace template parameter list with variadic parameter


Can someone help with how I can replace a list of template types with a variadic template parameter and create unique pointers of each type within the class.

The problem is I have a class

template <typename TA, typename TB1, typename TB2>
class Foo

which I want to extend to more than 2 TB types as such

template <typename TA, typename ... TB>
class Foo

Full example code

#include <iostream>
#include <memory>

// Some concrete types
struct A
{
    A(int a) : a_(2*a){}
    void execute() const { std::cout << "A: " << a_ << "\n"; }
    int a_;
};
struct B1
{
    B1(int a, double b) : a_(a), b_(b){}
    void execute() const { std::cout << "B1: " << a_ << ", " << b_ << "\n"; }
    int a_; double b_;
};
struct B2
{
    B2(int a, double b) : a_(a*a), b_(b*b){}
    void execute() const { std::cout << "B2: " << a_ << ", " << b_ << "\n"; }
    int a_; double b_;
};

// Now declare a templated class
template <typename TA, typename TB1, typename TB2>
class Foo
{
private:
  // A unique pointer to each template type
  std::unique_ptr<TA>  m_a{};
  std::unique_ptr<TB1> m_b1{};
  std::unique_ptr<TB2> m_b2{};
public:
  Foo() = default;
  ~Foo() = default;
  
  void initialise(int a, double b)
  {
    m_a  = std::make_unique<TA>(a);
    m_b1 = std::make_unique<TB1>(a, b);
    m_b2 = std::make_unique<TB2>(a, b);
  }

  void execute() const
  {
    m_a->execute();
    m_b1->execute();
    m_b2->execute();
  }
};

int main(int, char**)
{
  // Use templated class
  Foo<A,B1,B2> foo;
  foo.initialise(5, 3.14159);
  foo.execute();
  return 0;
}

Within the class Foo I declare, instantiate and use unique pointers to each of the types TB1 and TB2. With just 2 template parameters, I can easily do this. However, I don't know how to do this with a variadic template parameter.

Essentially I would like to change Foo to look like this:

template <typename TA, typename ... TB>
class Foo
{
private:
  // A unique pointer to each template type
  std::unique_ptr<TA>  m_a{};
  // TODO: Declare list or tuple of unique pointers to each of the types in TB ...
public:
  Foo() = default;
  ~Foo() = default;
  
  void initialise(int a, double b)
  {
    m_a  = std::make_unique<TA>(a);
    // TODO: Instantiate the list/tuple of unique pointers
  }

  void execute() const
  {
    m_a->execute();
    // TODO: Call execute on the list/tuple of unique pointers
  }
};

The parts that I have labelled TODO are the ones I don't know (even if this can be done at all).

Some notes

  • The TB classes are distinct and do not necessarily have a common base class
  • The TB classes do not have a default constructor.
  • We can assume that the TB classes have a non-default constructor with the same signature.

Of course I'm also open to a completely different approach which allows me to have up to 6 TB classes without much code duplication.


Solution

  • Something along these lines, perhaps (not tested):

    template <typename TA, typename ... TB>
    class Foo
    {
    private:
      std::unique_ptr<TA>  m_a{};
      std::tuple<std::unique_ptr<TB>...> m_bs;
    
    public:
      void initialise(int a, double b)
      {
        m_a  = std::make_unique<TA>(a);
        std::apply([&](std::unique_ptr<TB>&... p) {
            (void(p = std::make_unique<TB>(a, b)), ...);
          },
          m_bs);
      }
    
      void execute() const
      {
        m_a->execute();
        std::apply([&](std::unique_ptr<TB>&... p) {
            (void(p->execute()), ...);
          },
          m_bs);
      }
    };