0

How to make an optional template parameter with the base class using CRTP in the following code ?

template <unsigned int BYTES, OPTIONAL typename DerivedPrinter = MonoPrinter>  //DerivedPrinter should be optional. If it is not specified then it should default to MonoPrinter.
class MonoPrinter
{
protected:
    unsigned char CtrlCodes[BYTES] = { 0xFF };   //A code to initialize the printer

public:
    MonoPrinter()
    {
    }

    DerivedPrinter& print(const char* d)
    {
        for (int i=0; i<sizeof(CtrlCodes); i++)
          SendCtrlCode(CtrlCodes[i]);     //Initialize the printer and send additional control codes for color, font, etc...

        printf("Print Me: %s\n", d);  //This would actually send the string of chars to the printer (not to stdout) for printing
        return static_cast<DerivedPrinter&>(*this);     //Return a reference to the Derived Printer a la CRTP
    }
};

template <unsigned int BYTES>
class ColorPrinter : public MonoPrinter<BYTES, ColorPrinter>
{
public:
    ColorPrinter() : MonoPrinter()
    {
        static_assert(sizeof(CtrlCodes) >= 4);
        CtrlCodes[1] = 0xAA;
        CtrlCodes[2] = 0xBB;
        CtrlCodes[3] = 0xC0;
    }

    ColorPrinter& SetColor(unsigned char c)
    {
        CtrlCodes[3] = c;
        return *this;
    }
};


void main(void)
{
    MonoPrinter<1> iMonoPrinter;
    ColorPrinter<4> iColorPrinter;

    iMonoPrinter.print("Hello World").print(" we have no color");
    iColorPrinter.print("Hello World").SetColor(BLUE).print(" in Living Color");
}

P.S.
The above code is a contrived and abridged for simplicity.
The "BYTES" template parameter is not optional and it always must be specified.
I have other problems with this code, but the main one is how to make the "DerivedPrinter" template parameter optional, so it does not always have to be specified ...and when it is not - it should default to the base class itself.

George Robinson
  • 1,500
  • 9
  • 21

2 Answers2

2

I guess you can (see code below), but I think it's not necessary in this case (see second example).

First example, with the optional template parameter (note that here the PrinterTpl template inherits directly from the concrete BasePrinter, so all derived classes, MonoPrinter and ColorPrinter here, are inheriting from BasePrinter):

template <unsigned int BYTES>
class BasePrinter
{
protected:
    unsigned char CtrlCodes[BYTES] = { 0xFF };

public:
    BasePrinter()
    {
        SendCtrlCode(CtrlCodes[0]);  //Initialize the printer
    }
};

template <unsigned int BYTES, typename DerivedPrinter = BasePrinter<BYTES>>  //DerivedPrinter should be optional. If it is not specified then it should default to PrinterTpl.
class PrinterTpl : public BasePrinter<BYTES>
{
public:
    PrinterTpl() : BasePrinter<BYTES>()
    {
    }

    DerivedPrinter& print(const char* d)
    {
        printf("Data: %s\n", d);
        return static_cast<DerivedPrinter&>(*this);     //Return a reference to the Derived Printer a la CRTP
    }
};

template <unsigned int BYTES>
class MonoPrinter : public PrinterTpl<BYTES, MonoPrinter<BYTES>>
{
public:
    MonoPrinter() : PrinterTpl<BYTES, MonoPrinter<BYTES>>()
    {
    }
};

template <unsigned int BYTES>
class ColorPrinter : public PrinterTpl<BYTES, ColorPrinter<BYTES>>
{
public:
    ColorPrinter() : PrinterTpl<BYTES, ColorPrinter<BYTES>>()
    {
        static_assert(sizeof(this->CtrlCodes) >= 4, "CtrlCodes too small");
        this->CtrlCodes[1] = 0xC1;
        this->CtrlCodes[2] = 0xC2;
        this->CtrlCodes[3] = 0xC3;
    }

    ColorPrinter& SetColor(int c)
    {
        assert(c < sizeof(this->CtrlCodes));
        SendCtrlCode(this->CtrlCodes[c+1]);
        return *this;
    }
};

Second example, no template optional parameter (here the template PrinterTpl doesn't need to inherit from a base):

template <unsigned int BYTES, typename ConcretePrinter>
class PrinterTpl
{
protected:
    unsigned char CtrlCodes[BYTES] = { 0xFF };

public:
    PrinterTpl()
    {
        SendCtrlCode(this->CtrlCodes[0]);  //Initialize the printer
    }

    ConcretePrinter& print(const char* d)
    {
        printf("Data: %s\n", d);
        return static_cast<ConcretePrinter&>(*this);     //Return a reference to the Derived Printer a la CRTP
    }
};

template <unsigned int BYTES>
class MonoPrinter : public PrinterTpl<BYTES, MonoPrinter<BYTES>>
{
public:
    MonoPrinter() : PrinterTpl<BYTES, MonoPrinter<BYTES>>()
    {
    }
};

template <unsigned int BYTES>
class ColorPrinter : public PrinterTpl<BYTES, ColorPrinter<BYTES>>
{
public:
    ColorPrinter() : PrinterTpl<BYTES, ColorPrinter<BYTES>>()
    {
        static_assert(sizeof(this->CtrlCodes) >= 4, "CtrlCodes too small");
        this->CtrlCodes[1] = 0xC1;
        this->CtrlCodes[2] = 0xC2;
        this->CtrlCodes[3] = 0xC3;
    }

    ColorPrinter& SetColor(int c)
    {
        assert(c < sizeof(this->CtrlCodes));
        SendCtrlCode(this->CtrlCodes[c+1]);
        return *this;
    }
};

If I am not mistaken, this should achieve your goal and it's cleaner in my opinion.

Marco Pantaleoni
  • 2,529
  • 15
  • 14
  • Indeed the second solution is cleaner - only one additional struct/class !. To use this, I would have to rename my current MonoPrinter to PrinterTpl and write a new stubby MonoPrinter instead - which is fine. However, I have many constructors and one of them takes variadic params. They will all end up in the PrinterTpl now. How to make them accessible from the new stubby MonoPrinter without redefining them manually? – George Robinson Apr 12 '18 at 11:49
  • OMG! Now the data member CtrlCodes is not accessible from derived classes unless it is prefixed like this: PrinterTpl>::CtrlCodes ...or like this: this->CtrlCodes, which incurs the penalty of additional indirection. – George Robinson Apr 12 '18 at 12:41
  • 1
    there should be no indirection and no penalty, should be only a syntactic nuisance due to the later instantiation of the template parent (see for example https://stackoverflow.com/a/6592617/1363486) – Marco Pantaleoni Apr 12 '18 at 13:11
  • The construct PrinterTpl>::CtrlCodes might be a syntactic nuisance, but the construct this->CtrlCodes involves dereferencing a this pointer, doesn't it? – George Robinson Apr 12 '18 at 13:41
  • Why couldn't this entire problem be solved by a variadic parameter pack of the base template ? – George Robinson Apr 12 '18 at 13:45
  • from the point of view of machine code generated, this->CtrlCodes is exactly equivalent to the PrinterTpl<...>::CtrlCodes, like when in inside a normal instance method you access an hypothetical "aMember" it's the same if you use "this->aMember": the compiler derefences the this pointer in any case, only with the former syntax it's implicit. – Marco Pantaleoni Apr 12 '18 at 15:00
  • what do you mean by solving it with a variadic parameter pack? How would you use it? – Marco Pantaleoni Apr 12 '18 at 15:03
  • Did you have a chance to consider the question from my 1st comment? "How to make them accessible from the new stubby MonoPrinter without redefining them manually? " – George Robinson Apr 12 '18 at 16:39
  • Do you need all these different constructors in all the concrete classes or only in MonoPrinter? In the former case I believe you'll have to write those manually (if I am not missing something obvious), while in the second, you don't need the constructors in the template PrinterTpl, so you can write them just once in MonoPrinter. – Marco Pantaleoni Apr 12 '18 at 16:49
  • Most of the time I need them in all if the concrete classes. The best solution I was able to find was using the `using` statements. See this: https://stackoverflow.com/questions/49805048/a-directive-to-inherit-all-protected-and-public-members-of-a-templated-base-clas BTW: I was treated very unfairly in that thread. Almost nobody understood the problem and treated me like an idiot who does not understand basic class inheritance. – George Robinson Apr 12 '18 at 21:50
  • I'm very sorry to hear that, respect should be the basis of every discourse... I believe the multiple constructors problem and the problem references in the other thread where you proposed `using` are two different issues. You can avoid resorting to `using` by accessing the members with `this->` as shown in my example above. Rest assured that there is NO PENALTY for that: here you can see that the generated code is *identical* when accessing a member without `this` and with `this`: https://godbolt.org/g/hKsUp7 (see `sum_1` and `sum_2` generated code). – Marco Pantaleoni Apr 13 '18 at 08:06
1

The key to being able to write MonoPrinter<1> instead of MonoPrinter<1,dummy> and making the 2nd template parameter optional was the following conditional typedef typenameinside the base class template:

typedef typename std::conditional< std::is_same<Derived, void >::value, MonoPrinter, Derived >::type DerivedPrinter;  //Default to the MonoPrinter class if Derived == void

The code below now compiles without errors and does not require the creation of a 3rd class template. See: https://godbolt.org/g/awuck7

#include <type_traits>
#include <stdio.h>

#define BLUE 3

template <unsigned int BYTES, typename Derived = void>
class MonoPrinter
{
    typedef typename std::conditional< std::is_same<Derived, void >::value, MonoPrinter, Derived >::type DerivedPrinter;   //Default to the MonoPrinter class if Derived == void

protected:
    unsigned char CtrlCodes[BYTES];
    const unsigned char FinCode = 0xFF;

public:
    void SendCtrlCode(unsigned char c)
    {
        printf("<%02X>", c);    //This would actually send the string of control chars to the printer (not to stdout)
    }

    void InitializePrinter(void)
    {
        printf("\n");
        SendCtrlCode(CtrlCodes[0]);
        SendCtrlCode(0x00);
        SendCtrlCode(FinCode);
    }

    MonoPrinter()
    {
        CtrlCodes[0] = 0xEE;  //Set the default printer escape code
        InitializePrinter();
    }

    MonoPrinter(unsigned char c)
    {
        CtrlCodes[0] = c;  //A custom printer escape code
        InitializePrinter();
    }

    DerivedPrinter& print(const char* d)
    {
        for (int i = 0; i < sizeof(CtrlCodes); i++)
            SendCtrlCode(CtrlCodes[i]);     //Initialize the printer and send additional control codes for color, font, etc...
        SendCtrlCode(FinCode);

        printf("%s", d);  //This would actually send the string of chars to the printer (not to stdout) for printing
        return static_cast<DerivedPrinter&>(*this);     //Return a reference to the Derived Printer a la CRTP
    }

    int FooFn()
    {
        return 333;
    }
};


template <unsigned int BYTES>
class ColorPrinter : public MonoPrinter<BYTES, ColorPrinter<BYTES>>
{
protected:
    using MonoPrinter<BYTES, ColorPrinter<BYTES>>::CtrlCodes;
    //using MonoPrinter<BYTES, ColorPrinter<BYTES>>::FinCode;

public:
    //using MonoPrinter<BYTES, ColorPrinter<BYTES>>::MonoPrinter;
    using MonoPrinter<BYTES, ColorPrinter<BYTES>>::FooFn;
    //using MonoPrinter<BYTES, ColorPrinter<BYTES>>::InitializePrinter;
    //using MonoPrinter<BYTES, ColorPrinter<BYTES>>::SendCtrlCode;

    ColorPrinter()
    {
        static_assert(sizeof(this->CtrlCodes) >= 4, "CtrlCodes too small");
        CtrlCodes[1] = 0xDD;
        CtrlCodes[2] = 0xEE;
        CtrlCodes[3] = 0xC0;  //Default Color value
    }

    ColorPrinter(unsigned char c) : MonoPrinter<BYTES, ColorPrinter<BYTES>>::MonoPrinter(c)
    {
        static_assert(sizeof(this->CtrlCodes) >= 4, "CtrlCodes too small");
        CtrlCodes[1] = 0xDD;
        CtrlCodes[2] = 0xEE;
        CtrlCodes[3] = 0xC0;  //Default Color value
    }

    ColorPrinter& SetColor(unsigned char c)
    {
        CtrlCodes[3] = c;
        return *this;
    }

    int BooFn()
    {
        return FooFn() + 1;
    }
};


int main(void)
{
    MonoPrinter<1> iMonoPrinter;
    ColorPrinter<4> iColorPrinter(0xCC);

    iMonoPrinter.print("Hello World").print(" we have no color \n");
    iColorPrinter.print("Hello World").SetColor(BLUE).print(" in Living Color \n");

    printf(" %d\n", iColorPrinter.FooFn());
    printf(" %d\n", iColorPrinter.BooFn());

    return 0;
}
George Robinson
  • 1,500
  • 9
  • 21