Search code examples
c++templatesc++14variadic-templatestemplate-meta-programming

How to pass other template parameter when template class uses parameter pack?


I would like to create template class that implements print() method for each type passed as template parameters.

Something like that:

class Interface
{
public:
    virtual ~Interface() = default;
    virtual void print(int) = 0;
    virtual void print(double) = 0;
};
X x<int, double, Interface>;

class X has public method void print() and it works.

The whole code below:

#include <iostream>
#include <type_traits>

struct Printer
{
    void print(int i) {std::cout << i << std::endl; }
    void print(double d) {std::cout << d << std::endl; } 
};

class Interface
{
public:
    virtual ~Interface() = default;
    virtual void print(int) = 0;
    virtual void print(double) = 0;
};

template <typename... Args>
class X;

template <typename Interface>
class X<Interface> : public Interface
{
    static_assert(std::is_abstract<Interface>::value, "Last argument should be an interface");

public:
    X(Printer printer) {}
    using Interface::print;
};

template <typename Arg, typename... Args>
class X<Arg, Args...> : public X<Args...>
{
    using Parent = X<Args...>;

public:
    using Parent::print;

    X(Printer printer_): Parent(printer), printer{printer_} {}
    void print(Arg arg) override { printer.print(arg); }

private:
    Printer printer;
};

int main()
{
    Printer printer;
    X<double, int, Interface> x(printer);
    x.print(5);
}

As you see class X uses Printer class but the problem is that I would like to have Printer as a template parameter...

Is it possible? How to do that?


Solution

  • As you see class X uses Printer class but the problem is that I would like to have Printer as a template parameter...

    Is it possible? How to do that?

    Sorry but... I don't see the problem (with a great simplification suggested by Story Teller: place a single Printer object in the ground case case)

    template <typename...>
    class X;
    
    template <typename Printer, typename Interface>
    class X<Printer, Interface> : public Interface
     {
       static_assert(std::is_abstract<Interface>::value,
                     "Last argument should be an interface");
    
       public:
          X (Printer p0) : printer{p0}
           { }
    
          using Interface::print;  // why?
    
       protected:
          Printer printer;
     };
    
    template <typename Printer, typename Arg, typename... Args>
    class X<Printer, Arg, Args...> : public X<Printer, Args...>
     {
       using Parent = X<Printer, Args...>;
    
       public:
          using Parent::print;
          using Parent::printer;
    
          X(Printer printer_): Parent{printer_} {}
    
          void print(Arg arg) override { printer.print(arg); }
     };
    
    // ....
    
    X<Printer, double, int, Interface> x(printer);
    

    Off topic: attention: you're using printer uninitialized

    X(Printer printer_): Parent(printer), printer{printer_} {}
    

    I suppose you should write Parent(printer_)