Template C++ problem in porting ITEADLIB_Nextion

I think my C++ is too rusty for this (never really got the hang of templates :blush:).
So I’m calling for help on the most amazing community, although this is a pure C++ question and not a Particle one (although it is in connection with a library port for Particle).

I’ve got one controler class NexDisplay that should house a map of components derived off NexObject base class.
So I originally went this way (.h & .cpp seperated of course ;-))

TL;DR: The most puzzling error messages I can’t wrap my head round are at the bottom of this post

class NexDisplay
{
public:
  ...
  NexObject& add(uint8_t pageID, uint8_t compID, const char* name, void *value = NULL, bool withEvents = false, bool global = false)
  {
    uint16_t key = (pageID << 8) || compID;
    std::pair<std::map<uint16_t, NexObject>::iterator, bool> ret;
    ret = __components.emplace(std::piecewise_construct,
      std::forward_as_tuple(key),
      std::forward_as_tuple(pageID, compID, name, value));
    return ret.first->second;
  }

  NexObject& add(NexPage& page, uint8_t compID, const char* name, void *value = NULL, bool withEvents = false, bool global = false)
  {
    uint16_t key = (page.__pid << 8) || compID;
    std::pair<std::map<uint16_t, NexObject>::iterator, bool> ret;
    ret = __components.emplace(std::piecewise_construct,
                               std::forward_as_tuple(key),
                               std::forward_as_tuple(*this, page, compID, name, value));
    return ret.first->second;
  }
  ...
protected:
  std::map<uint16_t, NexObject>  __components; /* map to hold all components for standard access */
  ...
friend class NexPage;    // grant access to protected functions / fields for NexPage
};

class NexObject 
{
public:
  NexObject(NexDisplay& display, NexPage& page, uint8_t cid, const char *name, void *value = NULL)
  {
    this->__display = &display;
    this->__page = &page;
    this->__pid = page.__pid;
    this->__cid = cid;
    this->__name = name;
    this->__value = value;
  }
  ...
protected: /* data */
  NexDisplay* __display;  /* pointer to the owning display for hardware access   */ 
  NexPage*    __page;     /* pointer to the owning page (NexPages are selfowned) */
  uint8_t __pid = 0;      /* Page ID (will be set even for global compnents)     */
  uint8_t __cid = 0;      /* Component ID                                        */
  const char *__name;     /* An unique name                                      */
  void *__value;

friend class NexDisplay; // grant access to protected functions / fields for NexDisplay
};

class NexTouch: public NexObject
{
public
  NexTouch(NexDisplay& display, NexPage& page, uint8_t cid, const char *name, void *value = NULL)
  : NexObject(display, page, cid, name, value) { }
  ...
};

class NexPage: public NexTouch
{
public:
  NexPage(NexDisplay& display, uint8_t pid, const char *name, void *value = NULL) 
  : NexTouch(display, *this, 0, name, value) { this->__pid = pid; }

  ...
};

NexObject& NexPage::add(uint8_t compID, const char* name, void* value, bool withEvents, bool global)
{
  return __display->add(*this, compID, name, value, withEvents, global);
}

class NexText  : public NexTouch { ... };
class NexButton: public NexTouch { ... };
...

And having set things up like this, I’d use it like that

NexDisplay   display;  // the display is the root element
 NexPage     &pgMain = display.add(0, 0, "pg0");
   NexText   &nxText = (NexText   &)pgMain.add(1, "txt0");
   NexButton &nxBtn0 = (NexButton &)pgMain.add(2, "btn0");
   NexButton &nxBtn1 = (NexButton &)pgMain.add(3, "btn1");

But I didn’t like all the casting and thought, why not give templates a try, but I just seem to not get it to work.

I tried to alter the above like this (no worries, not as long again)

template<class T> T& NexDisplay::add(uint8_t pageID, uint8_t compID, const char* name, void* value, bool withEvents, bool global)
{
  uint16_t key = (pageID << 8) || compID;
  std::pair<std::map<uint16_t, NexObject>::iterator, bool> ret;
  ret = __components.emplace(std::piecewise_construct,
    std::forward_as_tuple(key),
    std::forward_as_tuple(pageID, compID, name, value));
  return (T)ret.first->second;
}

template<class T> T& NexDisplay::add(NexPage& page, uint8_t compID, const char* name, void* value, bool withEvents, bool global)
{
  uint16_t key = (page.__pid << 8) || compID;
  std::pair<std::map<uint16_t, NexObject>::iterator, bool> ret;
  ret = __components.emplace(std::piecewise_construct,
                             std::forward_as_tuple(key),
                             std::forward_as_tuple(*this, page, compID, name, value));
  return (T)ret.first->second;
}

template<class T> T& NexPage::add(uint8_t compID, const char* name, void* value, bool withEvents, bool global)
{
  return __display->add<T>(*this, compID, name, value, withEvents, global);
}

// usage

NexDisplay  display;  // the display is the root element
 NexPage    &pgMain = display.add(0, 0, "pg0");
  NexText   &nxText = pgMain.add(1, "txt0");
  NexButton &nxBtn0 = pgMain.add(2, "btn0");
  NexButton &nxBtn1 = pgMain.add(3, "btn1");

But I just can’t get this to build - I’ve tried all sorts of things, but obviously not the right one.
This one e.g. gave that error

Timer.cpp:88:50: error: no matching function for call to 'NexDisplay::add(int, int, const char [4])'
   
NexPage       &pgMain = display.add(0, 0, "pg0");
In file included from ITEADLIB_Nextion.h:18:0,
                 
from Timer.cpp:28:
NexDisplay.h:99:24: 
note: template<class T> T& NexDisplay::add(uint8_t, uint8_t, const char*, void*, bool, bool)
   
template<class T> T& add(uint8_t pageID, uint8_t compID, const char* name, void *value = NULL, bool withEvents = false, bool global = false);

NexDisplay.h:99:24: note:   
template argument deduction/substitution failed:
Timer.cpp:88:50: note:   
couldn't deduce template parameter 'T'
   
NexPage       &pgMain = display.add(0, 0, "pg0");
Timer.cpp:89:47: error: no matching function for call to 'NexPage::add(int, const char [5])'
     
NexText     &nxText = pgMain.add(1, "txt0");

So I changed it this way (which somehow counteracts the purpose of getting rid of the need to provide the type)

NexDisplay  display;  // the display is the root element
 NexPage    &pgMain = display.add<NexPage >(0, 0, "pg0");
  NexText   &nxText = pgMain.add<NexText  >(1, "txt0");
  NexButton &nxBtn0 = pgMain.add<NexButton>(2, "btn0");
  NexButton &nxBtn1 = pgMain.add<NexButton>(3, "btn1");
Then I get this (which puzzles me completely) ../../../build/target/user/platform-6/libuser.a(Timer.o): In function `__static_initialization_and_destruction_0': Timer.cpp:88: undefined reference to `NexPage& NexDisplay::add(unsigned char, unsigned char, char const*, void*, bool, bool)' Timer.cpp:89: undefined reference to `NexText& NexPage::add(unsigned char, char const*, void*, bool, bool)' Timer.cpp:90: undefined reference to `NexButton& NexPage::add(unsigned char, char const*, void*, bool, bool)' Timer.cpp:91: undefined reference to `NexButton& NexPage::add(unsigned char, char const*, void*, bool, bool)' collect2: error: ld returned 1 exit status

OK, got this solved, by putting the implementation of the templated functions into the headers - duh :stuck_out_tongue_closed_eyes:

But I now have a “unsolvable” situation where I’m chasing my own tail.
Having the implementation inside the header renders forward declaration of the needed classes impossible but #including the needed class causes a

In file included from NexPage.h:26:0,
                 from Nextion.h:41,
                 from ITEADLIB_Nextion.h:17,
                 from ITEADLIB_Nextion.cpp:17:
NexDisplay.h: In member function 'T& NexDisplay::add(NexPage&, uint8_t, const char*, void*, bool, bool)':
NexDisplay.h:117:25: error: invalid use of incomplete type 'class NexPage'
     uint16_t key = (page.__pid << 8) || compID;
                         ^
In file included from Nextion.h:33:0,
                 from ITEADLIB_Nextion.h:17,
                 from ITEADLIB_Nextion.cpp:17:
NexObject.h:31:7: error: forward declaration of 'class NexPage'
 class NexPage;     // forward declare required class
       ^

I’ll put a complete - not building - branch dumbplate on GitHub, if anybody wants to be so kind and have a go at this.

As final goal (if possible) I’d still like it to have the version without the need to provide the type <Nex....> working.

Any help will be appreciated :smiley:

I had the same problem and error using templates and your solution worked. I’d be really curious to understand what is the problem here if you have additional info.

Thanks,

Guillaume

The reason for this is - quite logical when you think about it - that it’s a compile time decission to be made what template to use and which type to “insert”, but at compile time only the header is known. The implementation (contents of the .cpp file) only gets added by the linker later on (link time).

Indeed! I really got crazy over this.
While trying to debug this I looked how templates were used in the Particle firmware code and it’s actually implemented the same way. Ex: https://github.com/spark/firmware/blob/38a5e80d17cda97e1c30d63d9ec2a681f36ae21a/wiring/inc/spark_wiring_timer.h

Thanks a lot!

Guillaume

1 Like