CRTP (Curiously Recurring Template Pattern) using a generic base template class instead of a derived class

I've been researching CRTP recently and came up with the idea of ​​creating a basic base template using CRTP.

// Example.h
namespace A {
    template <class TClass, typename T>
    class Example {
    public:
        Example(T &someStruct) : m_someStruct_(someStruct)
        {
        }

        ~Example() 
        {
            DoThis();
        }

    public:
        void DoThis()
        {
            static_cast<TClass*>(this)->DoThat(m_someStruct_);
        }

    private:
        T m_someStruct_;
    };
}

// AsArgument.h
namespace A {
    class AsArgument : public Example <AsArgument, SomeStruct> {
    friend class Example <AsArgument, SomeStruct>;
    private:
        void DoThat(SomeStruct &someFun)
        {
            // Do something to someFun object.
            // yehey(someFun);
            printf("I want to do that! \n");
        }
    };
}

      

My goal is to use a base class object to access the functionality of the derived class and at the same time decouple the base and derived implementation by including only the base class header file and pass the derived class declaration as a template argument.

I know the basics of what I can do with incomplete types, but I cannot find information on templates.

Is it okay to pass the TDerived class argument instead of including the header file?

// SomeFile.cpp
#include "Example.h"

class A::AsArgument; // Forward declare this instead of including the AsArgument.h header file

namespace B {
    void SomeClass::DoSomething()
    {
        SomeStruct fun;
        Example <AsArgument, SomeStruct> example(fun);
    }
}

      

I'm not sure if this is good design when creating a base base template, but my goal after creating a base class is to easily derive classes from it and define the base class implementation at compile time. It's actually some kind of combination of RAII and CRTP.

I can actually achieve this by including the "AsArgument.h" file with "Example.h", but the separation between base and implementation will be lost. And I keep getting compile error when I try to redirect the declaration of the AsArgument class (probably due to namespace issues that I don't quite understand about).

Any advice or is it such a design, even effective and valid?

+3


source to share


1 answer


I'm not entirely sure what the purpose of the design is here, but the rules about incomplete types apply the same regardless of whether you're talking about templates, you just need to think about where the template is instantiated.

In your case, you are trying to avoid including AsArgument.h in SomeFile.cpp. However, you create an instance of the Example class template with the AsArgument class. This means that when you compile SomeFile.cpp, this translation unit knows nothing about the AsArgument class (because it doesn't see its declarations in the .h file), only that it exists.

However, as you might expect, there is not much you can do with a class if you only know that it exists. You cannot even hold it with dignity, since you do not know its size. You cannot use any interface. In your example, the compiler doesn't know that AsArgument :: DoThat even exists (it doesn't need to know what it does, which can be left to the linker). Remember the example is generated in SomeFile.cpp, so when the compiler needs to know that DoThat exists.



So, you need AsArgument.h. With a regular class, you can just put the declaration in the .h file and put the definitions (implementation) in the .cpp file. But AsArgument is a templated class, so you can't do that at all. You can only do this for templates if you are configured with a limited number of classes that are known in advance and are willing to explicitly create a template for all of them.

I cannot comment on a too big picture because I don't know what you are trying to do. I'm not really sure if CRTP is even the right fit for you. CRTP is useful for some things, but it is not the first tool I turn to. Actually, now that I think about it, I rarely use it. Most of the time, if I'm going to use template based polymorphism I could just keep the "child" directly and skip the base entirely, in many cases I don't feel like the base is buying me enough.

I would recommend including any compiler errors in future SO questions. Good luck!

+1


source







All Articles