0

I'm currently transitioning from C# to c++ and I'm constantly hitting road blocks. I got an event handler system from a tutorial and try to adapt it to my needs, but there is an error which I can't understand:

Event:

#pragma once
class Event
{
protected:
   virtual ~Event() {};
};

Event Hander:

#pragma once
#include "Event.h"
#include "TypeInfo.h"
#include "HandlerFunctionBase.h"
#include <map>
#include <typeindex>

class EventHandler
{

public:
    void HandleEvent(const Event*);

    template < class T, class EventT >
    void RegisterEventFunc(T*, void (T::*memFn)(EventT*));

private:
    typedef std::map<std::type_index, HandlerFunctionBase* > Handlers;
    Handlers _handlers;
};

[...]

#include "EventHandler.h"

template < class T, class EventT >
void EventHandler::RegisterEventFunc(T* obj, void (T::*memFn)(EventT*))
{
    _handlers[std::type_index(typeid(EventT))]=
    new MemberFunctionHandler< T, EventT >(obj, memFn);
}

void EventHandler::HandleEvent(const Event* event)
{
    Handlers::iterator it = _handlers.find(std::type_index(typeid(*event)));
    if(it != _handlers.end())
    {
        it->second->exec(event);
    }
}

HandlerFunctionBase:

#pragma once
#include "Event.h"
class HandlerFunctionBase
{
public:
  virtual ~HandlerFunctionBase() {};
  void exec(const Event* event) {call(event);}

private:
  virtual void call(const Event*) = 0;
};

MemberFunctionHandler:

#pragma once
#include "handlerfunctionbase.h"
template < class T, class EventT >

class MemberFunctionHandler : public HandlerFunctionBase
{
public:
  typedef void (T::*MemberFunc)(EventT*);
  MemberFunctionHandler(T* instance, MemberFunc memFn) : _instance(instance), _function(memFn) {};

  void call(const Event* event)
  {
    (_instance->*_function)(static_cast< EventT* >(event));
  }

private:
  T* _instance;
  MemberFunc _function;
};

LogHandler

(My own class, first try to use the system)

#pragma once
#include "EventHandler.h"
#include "LogEvent.h"
class LogHandler
{
public:
    LogHandler(EventHandler*);
    ~LogHandler(void);
private:
    void Handle(LogEvent*);
};
#[...]
#include "LogHandler.h"


LogHandler::LogHandler(EventHandler *handler)
{
    //This line causes the error
    handler->RegisterEventFunc<LogHandler, LogEvent>(this, &LogHandler::Handle);
}


LogHandler::~LogHandler(void)
{
}
void LogHandler::Handle(LogEvent* e)
{
}

What I'm getting when trying to compile this:

Error 1 error LNK2019: unresolved external symbol "public: void __thiscall EventHandler::RegisterEventFunc(class LogHandler *,void (__thiscall LogHandler::*)(class LogEvent *))" (??$RegisterEventFunc@VLogHandler@@VLogEvent@@@EventHandler@@QAEXPAVLogHandler@@P81@AEXPAVLogEvent@@@Z@Z) referenced in function "public: __thiscall LogHandler::LogHandler(class EventHandler *)" (??0LogHandler@@QAE@PAVEventHandler@@@Z) D:\Dropbox\C++\D-Tris\D-Tris\D-Tris\LogHandler.obj D-Tris

How is RegisterEventFunc unresolved? It's clearly implemented !?

jonhopkins
  • 3,844
  • 3
  • 27
  • 39
pixartist
  • 1,137
  • 2
  • 18
  • 40
  • 2
    You need to put the implementation of templated method into the header file. – v154c1 Nov 22 '13 at 13:58
  • Are your template function definitions in a .cpp file? If so, you need to make them accessible to the code that instantiates the templates. In practice, the easiest way to do this is to put them in the header file. – juanchopanza Nov 22 '13 at 13:58
  • I do? WHY? I thought c++ ignores files and simply stitches everything together !? – pixartist Nov 22 '13 at 14:03
  • Also: RegisterEventFunc expects a function with a non-constan event as parameter, the static cast in MemberFunctionHandler expects a const event. How can this work? In other words: (_instance->*_function)(static_cast< EventT* >(event)); causes the Error 'static_cast' : cannot convert from 'const Event *' to 'LogEvent *' – pixartist Nov 22 '13 at 14:10

1 Answers1

1

The compiler has to know the types used to instantiate the template to actually generate the code. But it also generates code for every translation unit (one .cpp file) independently. It doesn't "ignore the files".

So at the point you have the definition of EventHandler::RegisterEventFunc, it can't know with which parameters it will be instantiated, if it's used (instantiated) outside this translation unit.

In LogHandler, it knows about the template EventHandler::RegisterEventFunc (from header file), but as there's no definition, it simply assumes theres instantiation of RegisterEventFunc<LogHandler, LogEvent> elsewhere and doesn't emit any error.

When it's linked together, the linker finds out that no one actually instantiated RegisterEventFunc<LogHandler, LogEvent> so it can't link it together and emits the error you see.

What you can do about it is either:

1) move the definition of EventHandler::RegisterEventFunc to EventHandler.h. (IMHO, this is the usual solution)

2) Or force explicit instantiation in EventHandler.cpp, like

template
void EventHandler::RegisterEventFunc<LogHandler, LogEvent>
       (LogHandler* obj, void (LogHandler::*memFn)(LogEvent*))

This "solution" breaks encapsulation, adds lots of dependencies and would be hell to maintain.

3) Or use exported templates. C++ supports (supported) templates the way you want to use them through the keyword export. It was supported only by Comeau and ICC (none of GCC, CLANG, MSVC ever supported this) and now it's removed from standard (In N3690, in [diff.cpp03.temp] (Annex. C.2.7, on page 1240) the standars says: A valid C++ 2003 declaration containing export is ill-formed in this International Standard.). Don't even try it, I added it just for sake of completeness.

Some related questions that may be interesting for you:

How do I explicitly instantiate a template function?

Using export keyword with templates

Why can templates only be implemented in the header file? (this actually seems like duplicate....)

EDIT: To your other question: You can't remove const qualifier from a variable by static_cast. Casting constness away is potentially dangerous and should be avoided, but if you absolutely need it, you can do it by const_cast< EventT* >(event).

Community
  • 1
  • 1
v154c1
  • 1,698
  • 11
  • 19