stuck writing a class that uses templates

Status
Not open for further replies.

KrisKasprzak

Well-known member
I'm trying to expand my flicker free print function to a class where I can pass in an unknown display object. I've used templates in the past and feel it may work

I'm stuck trying to figure out how to implement a template for an arguement in a class definition--assuming it can even be done

.ino code implementation

#include <FlickerFreePrint.h>
#include <ILI9341_t3.h>

// create the display object based on the ILI9341 library
ILI9341_t3 Display(PIN1, PIN2);

//Create my cute little print utility that will draw on the Display object (that uses the ILI9341 driver)
FlickerFreePrint Volts(&Display, COLOR_BLACK);

// print the volts to the display
Volts.print(23);

my class definition.

Code:
#ifndef FLICKER_FREE_PRINT
#define FLICKER_FREE_PRINT

#if ARDUINO >= 100
  #include "Arduino.h"
  #include "Print.h"
#else
  #include "WProgram.h"
#endif

#ifdef __cplusplus
  #include "Arduino.h"
#endif

#include "ILI9341_t3.h"

class FlickerFreePrint {

  public:

    template <typename T1> FlickerFreePrint(T1 *disp, uint16_t BackColor = 0)
    {
      T1 = disp;
      bc = BackColor;
    }

    void print(int Data) {
      dtostrf(Data, 0, 0, buf);
      len = strlen(buf);
      tHi = d->strPixelLen(cc);
      for (i = 0; i < len; i++) {
        if (buf[i] != obuf[i]) {
          d->fillRect (d->getCursorX() + bl, d->getCursorY() + bt, oc - d->getCursorX() + br, tHi + bb , bc);
          obuf[i] = buf[i];
        }
        d->setCursor(d->getCursorX(), d->getCursorY());
        d->print(buf[i]);
        c = d->getCursorX();
        if (i == len - 1) {
          oc = c;
        }
      }
    }

    void print(const char *buf) {
      len = strlen(buf);
      tHi = d->strPixelLen(cc);
      for (i = 0; i < len; i++) {
        if (buf[i] != obuf[i]) {
          d->fillRect (d->getCursorX() + bl, d->getCursorY() + bt, oc - d->getCursorX() + br, tHi + bb , bc);
          obuf[i] = buf[i];
        }
        d->setCursor(d->getCursorX(), d->getCursorY());
        d->print(buf[i]);
        c = d->getCursorX();
        if (i == len - 1) {
          oc = c;
        }
      }
    }

    void print(float Data, int Dec = 2) {
      dtostrf(Data, 0, Dec, buf);
      len = strlen(buf);
      tHi = d->strPixelLen(cc);
      for (i = 0; i < len; i++) {
        if (buf[i] != obuf[i]) {
          d->fillRect (d->getCursorX() + bl, d->getCursorY() + bt, oc - d->getCursorX() + br, tHi + bb , bc);
          obuf[i] = buf[i];
        }
        d->setCursor(d->getCursorX(), d->getCursorY());
        d->print(buf[i]);
        c = d->getCursorX();
        if (i == len - 1) {
          oc = c;
        }
      }
    }

    void SetPaintMargins(int MarginTop = 0, int MarginLeft = 0, int MarginBottom = 0, int MarginRight = 0) {
      bt = MarginTop;
      bl = MarginLeft;
      bb = MarginBottom;
      br = MarginRight;
    }

    void SetBackColor(uint16_t BackColor = 0) {
      bc = BackColor;
    }

  private:

    T1  *d;
    char    obuf[38];
    int     oc;
    int     c;
    uint16_t  bc;
    uint16_t  i;
    char    buf[38];
    int     tHi;
    byte    di;
    int     len;
    char    cc[2] = "D";
    int     bt = 0, bl = 0, bb = 0, br = 0;

};
 
Be honest never used templates before except for some simple stuff. But you might what to take a look at what @tonton81 did with his flexcan_t4 library. He makes the max use of templates for functions etc.
 
You can not have a templated constructor. And the following snippet doesn't make sense at all:

Code:
 template <typename T1> FlickerFreePrint(T1 *disp, uint16_t BackColor = 0)
 {
    T1 = disp;  // <== assign a pointer to a type??? Doesn't make sense...
    bc = BackColor;
}

Maybe you meant something like this (compiles and works):

Code:
#include "Arduino.h"

template<typename T1>
class FlickerFreePrint
{
  public:
  FlickerFreePrint(T1* display)
  {
    d = display;
  }

  void print(int Data)
  {
    d->println(Data);
  }

protected:
  T1 *d;
};

// usage: --------------

FlickerFreePrint<usb_serial_class> myPrinter(&Serial);  // you can pass any type here which has a print function

void setup()
{
  while(!Serial);

  myPrinter.print(42);
}

void loop()
{
}
 
Templates are awesome, objects are created during compile time, very different than run time objects. FlexCAN_T4 demonstrates how compile time objects can be linked to run time objects, in order to control the template object via a base class when it doesn't know the user object during compile time, in order to run internal functions. Be aware that static objects, are different if template parameters are different, Creating 2 different objects via constructor overloads doesnt share the same template class static variable/member. This is why TeensyCAN wouldn't able to be templated with node objects, as they all would have to share some types of static variables.

Circular_Buffer demonstrates a single .h file with all definitions and classes. If you have small, simple templates, no need for separate files, however, you may refer to FlexCAN_T4 to see how the source file uses another file for the class (.tpp (template)), all the definitions must have the identical template parameters for every function you create. In order to make the members less bloated, the templates were #define'd in the .h file and the #define was used on the template members. This offers an additional benefit if one were to add a compile time overload without editing every single function afterwards.

Theorhetically you can have any file extension you wish provided the source points to it via #include. .tpp is used as the norm naming convention for cpp template files


Code:
class FlickerFreePrint {
 public:
 template <typename T1> FlickerFreePrint(T1 *disp, uint16_t BackColor = 0) {

You cannot define a template inside a class, you must define the class itself as a template

Another benefit of using templates are only functions that you used are compiled, which can save resources, depending on your project. This is great isolation environment for multiple busses in hardware as well, as they have their own unique variables and methods, which in turn reduces code size and object checking during runtime
 
Last edited:
It's fine to define constructor (and any member function for that matter) as a template function though, but I guess that's not the behaviour what the OP is after.

i.e. this is just fine:
Code:
struct foo
{
  template<typename T> foo(T*) {}
};
In which case only the constructor of the class is a template function, not the entire class. But then member variables can't use the template type and the scope of the type is restricted to the function.

Beware though that every new instantiation of the template class/function duplicates the code (template bloat), which might be an issue for MCU's with tighter program memory or if you do excessive template instantiation.
 
That is true, but in the case of a T3.2 there is only one bus instantiated.
On a T3.6, you can instantiate 2 CAN controllers, except this time unlike last libraries, choosing one won't duplicate code and enable resources for the 2nd. The other issue arises when the old libraries run condition checks between both busses every loop cycle, whereas they are statically allocated by the template, where conditioning is not needed.

In both cases this they use similar functions, except the unused ones in templates are not compiled.

Templates do duplicate code, but in the case of the driver, the result is same but with better performance and less overhead. Thats the reason why TeensyCAN doesn't use templates, because the objects can't share the same buffer, so what happens? The buffers multiply with the objects and the single stream capture doesn't know which template to store it when they all need it. :)
 
Status
Not open for further replies.
Back
Top