1

I have a base class First and a derived class Second. In the base class there's a member function create and a virtual function run. In the constructor of Second I want to call the function First::create, which needs access to the implementation of its child class' run() function. A colleague recommended using function templates since First can't know it's child classes explicitly. Sounds weird? Here's some code:

First.h

#pragma once
#include <boost/thread/thread.hpp>
#include <boost/chrono/chrono.hpp>

class First
{
public:
    First();
    ~First();

    virtual void run() = 0;
    boost::thread* m_Thread;
    void create();

    template< class ChildClass >
    void create()
    {
        First::m_Thread = new boost::thread(
            boost::bind( &ChildClass::run , this ) );
    }
};

First.cpp

#include "First.h"

First::First() {}
First::~First() {}

Second.h

#pragma once
#include "first.h"

class Second : public First
{
public:
    Second();
    ~Second();

    void run();
};

Second.cpp

#include "Second.h"
Second::Second()
{
    First::create<Second>();
}

void Second::run()
{
    doSomething();
}

I'm getting an error at First::create<Second>(); saying Error: type name is not allowed. So what's the reason for this error? I assume I didn't quite get the whole mechanics of templates, yet - I'm very new to this topic.

dyp
  • 38,334
  • 13
  • 112
  • 177
DenverCoder21
  • 879
  • 3
  • 16
  • 34
  • Put the definition of the function template in the header. See, e.g. [this question](http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file) – dyp Aug 22 '13 at 10:05
  • Simply `template ` into First.h? Didn't have any effect on the error unfortunately. – DenverCoder21 Aug 22 '13 at 10:08
  • No, the whole function body. It needs to be in the header file since definitions of function template instantiations need to be available in each TU where they're used. – dyp Aug 22 '13 at 10:09
  • Ok I put the whole template...creat function body into First.h, but it still displays that error at `First::create();` in Second.cpp – DenverCoder21 Aug 22 '13 at 10:14
  • Please show us the headers as well. An [SSCCE](http://sscce.org/) would be appreciated. – dyp Aug 22 '13 at 10:18
  • [Second.h](http://pastebin.com/u0f8kmDa) [Second.cpp](http://pastebin.com/m7byCbgg) See my edit of the initial posting for First.h and First.cpp – DenverCoder21 Aug 22 '13 at 10:26
  • I removed the extra `First::` from the declaration/definition of `create` inside class `First`. – dyp Aug 22 '13 at 10:32
  • You have a template and non-template function `create` in your class `First` now. Remove the non-template one. The correct way to (forward-) declare a member function template would be `struct First { template < class ChildClass > void create(); };` – dyp Aug 22 '13 at 10:35
  • Thanks for the edit! Now the compiler tells me *Could not deduce template argument for 'ChildClass'* – DenverCoder21 Aug 22 '13 at 10:38
  • Did you leave the `create();` or changed it to `create();`? – dyp Aug 22 '13 at 10:40
  • Changed it to `create();`. I put the definition of create in the First.h into the struct First{} brackets. The compiler now says *Error: declaration of a member with the same name as its class*. When I change the name of the struct, I get the error `create is not a member`. – DenverCoder21 Aug 22 '13 at 10:43

2 Answers2

1

First you have to put template definitions in the header, or the linker will have trouble finding the instantiated templates. Second: now the pun's done, I assume you derived Second from First<Second>. This is called the "Curiously Recurring Template Pattern" (CRTP).

Since the base class is already specialized and has the type of the derived class carved into its type, there is no need to make any function of it a template (you didn't), and the base class' functions can be just called, without specifying template parameters:

template <class SubClass>
struct First {
  void create() {
    SubClass& sub = static_cast<SubClass&>(*this);
    mThread = make_unique<std::thread>( [&](){sub.run();} ); 
  }
};

struct Second : First<Second> {
  Second() { create(); }
  void run();
};

Some annotations:

  • You need not qualify accesses inside First
  • You can just call create, it's an inherited method, and since Second is no template, there is no need to add qualification or help the compiler with the lookup.
  • prefer std::thread over boost::thread, it has been in the standard for a while now.
  • prefer std::unique_ptr over raw pointers with ownership
  • I prefer lambdas over bind - reads more clear to me.
  • I made the two classes struct in order to have the member functions public by default and save one line in the example code. There is no difference to class except that.
Arne Mertz
  • 24,171
  • 3
  • 51
  • 90
  • Thanks alot for your answer. Sadly I'm obliged to use boost::thread. I have to admin I can't apply your answer to my code, yet. ;) – DenverCoder21 Aug 22 '13 at 10:37
1

Although you could use CRTP as "suggested" by Kerrek SB and Arne Mertz, here's a solution using a member function template:

class First
{
public:
    First();
    ~First();

    virtual void run() = 0;
    boost::thread* m_Thread;

    // vvvvvvvvvvv this declares a non-template member function `create`
    void create(); // (better remove it)

    // vvvvvvvvvv this declares a member function template `create`
    template< class ChildClass >
    void create();
};

For templates, you should provide the definition in the header file, since the definition must be available in every TU where it is required (and not only in one).

The easiest way to do this is to provide the definition of member function templates or members of class templates within the class itself:

class First
{
public:
    /* ... */

    template< class ChildClass >
    void create()
    {
        // AFAIK, the `bind` is not required
        First::m_Thread = new boost::thread(&ChildClass::run,
                                            static_cast<ChildClass*>(this));
    }
};

Now, if you want to call this member function template in the Second class, you'd have to explicitly provide the template argument:

void Second::any_function()
{
    create<Second>();
}

Some other (possible) issues with your code:

  • You don't have virtual dtors, therefore deleting a pointer to a base class subobject will invoke UB.
  • There needs to be a downcast, as &ChildClass::run is of type void (Second::*)().
  • create is called inside the ctor of Second, which will call Second::run (or whatever template argument you provide), the final override of that function in the class Second. If you override that function in a class derived from Second, that override won't be called in the ctor of Second.
  • Your dtor of First should either detach or join the boost thread, otherwise std::terminate will be called if the thread is still running when the dtor is called.
  • run doesn't have to virtual in this example, the exact same code would work w/o run being virtual or even declared in First.
dyp
  • 38,334
  • 13
  • 112
  • 177