简体   繁体   中英

Template member function in a non-template class - how to define and call

I've been playing with std::map lately and came up with a grand :D design to create a priority map - a map which contains various modes that are grouped based on their priorities.

I have the following class structure:

Mode
 |
 |----ModeSleep
 |----ModeFactorial

where Mode is:

class Mode
{
    std::string name; ///< Mode's name
    int priority;     ///< Mode's priority used for storing the Mode in a specific priority group in the priority map. Default: 0
  public:
    Mode();
    ///
    /// \brief Mode
    /// \param name Mode's name
    /// \param priority Mode's priority used for storing the Mode in a specific priority group in the priority map. Default: 0
    ///
    Mode(const std::string &name, const int priority=0);
    virtual ~Mode();

    std::string getName() const;
    void setName(const std::string &value);

    int getPriority() const;
    void setPriority(int value);

    ///
    /// \brief run is the part of a Mode which is executed by the ModeExecutor
    ///
    virtual void run() = 0;
};

On the other hand I have another class which uses Mode called PriorityMap with the following class definition:

class PriorityMap
{
    typedef std::pair<int, Mode *> ModeEntry;
    typedef std::map<int, Mode *> PriorityGroup;
    typedef PriorityGroup* PriorityGroup_Ptr;
    typedef std::map<int, PriorityGroup_Ptr> Priority;
    typedef Priority* Priority_Ptr;

    Priority_Ptr priorities;

    bool _insert(Mode *mode);
    Mode *_find(const std::string &name);
  public:
    PriorityMap();
    ~PriorityMap();

    void print();
    void insert(Mode *mode);
    template<class T> T *find(const std::string &name);
};

Below you can see an example of how the objects are initialized and called:

int main ()
{
  PriorityMap *priorities = new PriorityMap();
  ModeSleep *m1 = new ModeSleep("Sleep10", 0, 10);
  priorities->insert(m1);

  ModeSleep *m2 = new ModeSleep("Sleep5", 0, 5);
  priorities->insert(m2);

  ModeFactorial *m3 = new ModeFactorial("Factorial20", 1, 20);
  priorities->insert(m3);

  priorities->print();

  // Example for a correct match (both name and type) - ERROR!!!
  ModeSleep *foundM2 =  priorities->template find<ModeSleep>("Sleep5");
  if(foundM2)
    std::cout << "Found mode \"" << foundM2->getName() << "\" has time interval set to " << foundM2->getMilliseconds() << "ms" << std::endl;

  // Example for correct name match but incorrect type - ERROR!!!
  ModeSleep *foundM1 = priorities->template find<ModeSleep>("Factorial20");
  if(foundM1)
    std::cout << "Found mode \"" << foundM1->getName() << "\" has time interval set to " << foundM1->getMilliseconds() << "ms" << std::endl;

  delete priorities;

  return 0;
}

At first I didn't have any template-stuff for my find() (once I did I moved the original find() as a private _find() called inside the template version of find() ). My initial design (now _find() ) was:

Mode *PriorityMap::_find(const std::string &name)
{
  for(const auto& priorityGroup : *priorities)
    for(auto& modeEntry : *(priorityGroup.second))
      if(!name.compare((modeEntry.second->getName())))
        return modeEntry.second;

  return nullptr;
}

After running find() a couple of times I faced the problem that I had to manually downcast the returned pointer to the respective derivation of Mode (in my case just ModeSleep and ModeFactorial ). So I decided that adding a template-functionality to that function and also adding some feedback when calling it would be useful:

template<class T>
T *PriorityMap::find(const std::string &name)
{
  Mode *foundMode = _find(name);
  if(foundMode) {
    T *foundModeCast = dynamic_cast<T *>(foundMode);
    if(foundModeCast) {
      std::cout << "Found mode \"" << foundModeCast->getName() << "\"" << std::endl;
      return foundModeCast;
    }
    else {
      std::cout << "Found mode \"" << foundMode->getName() << "\" however specified type is invalid! Returning NULL" << std::endl;
      return nullptr;
    }
  }
}

As you can see according to my definition a found mode inside my priority map is based on two factors:

  • name is a match
  • given type is a match

I have a problem with the calling of my find() though and I my build breaks at the first usage of it with the following error:

In function `main':
undefined reference to `ModeSleep *PriorityMap::find<ModeSleep>(std::string const&);'

I haven't done much of template member functions and would appreciate some feedback on this. If you need more information do tell and will provide it.


PS: For those of you wondering about the way the way modes are found based on their names - I'm actually going to change my find() to return a vector of references since names are not unique in my case and I can have modes with the same name at different places in my priority map. Right now find() returns the first match but should suffice for the purpose of this post.

You need to define the function PriorityMap::find in your header file rather than your cpp file.

The problem is that with template functions, no instantiations are created in a given compilation unit unless the function instantiation is actually used in the said unit. The compilation unit in which you define your function is not the same one in which you actually use it, so in your case no instantiations are actually created. Later when it comes to linking the compilation unit(s) in which the function is used no definition is therefore found, so you get a linker error.

If you want to avoid defining the function in the header file then you can explicitly instantiate it in the cpp file in which it is defined. For example:

template void PriorityMap::find(MyClass);

The downside here is that this cpp file will have to know about all the types that will ever have to be used with PriorityMap::find

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM