Polymorphism using template and concepts

104 views Asked by At

I have this code where Derived is a subclass of Base and I need to have a different version of the func method assuming that proc method is common and have a lot of statements.

Does concept and template programming allow such requirement?

The requirement is : having two distinct classes having the same code but each one have his own (specific) func method.

Could I achieve this without inheritance? Hereunder is the code.

// templateInheritance.cpp

#include <iostream>

template <typename T>
class Base{
public:
    void func(){                    // (1)
        std::cout << "Base::func\n";
    }

    void proc()
    {
        // huge stuff goes here
        func();
        // huge stuff goes here
    }
};

template <typename T>
class Derived: public Base<T>{
public:
    void func(){                    // (2)
        std::cout << "Derived::func\n";
    }
};

int main(){

    std::cout << '\n';

    Derived<int> derived;
    derived.proc();
    Base<int> base;
    base.proc();            

    std::cout << '\n';

}

The expected output:

Derived::func
Base::func
2

There are 2 answers

0
Klaus On BEST ANSWER

My understanding is, that you want to have compile time polymorphism. That is really simple to implement and one common pattern is CRTP

For simplicity I split your Base in a real base and a second Base which can then be used for creating instances while the first is really only the common base.

There is no need for concepts nor any meta template programming.

Example:

#include <iostream>

template <typename T, typename MASTER>
class Base{
public:
    void proc()
    {    
        // huge stuff goes here
        static_cast<MASTER*>(this)->func();
        // huge stuff goes here
    }    
};

template <typename T>
class Base2: public Base< T, Base2<T>>
{
    public:
    void func(){                    // (2)
        std::cout << "Base::func\n";
    }    

};

template <typename T>
class Derived: public Base<T, Derived<T>>{
public:
    void func(){                    // (2)
        std::cout << "Derived::func\n";
    }    
};

int main(){

    std::cout << '\n';

    Derived<int> derived;
    derived.proc();
    Base2<int> base;
    base.proc();     

    std::cout << '\n';

}

see live demo

If you already have a C++23 compliant compiler, you can simplify much more by using explicit this parameter:

#include <iostream>

template <typename T>
class Base{
public:
    void func(){                    // (2)
        std::cout << "Base::func\n";
    }    
    void proc(this auto& self)
    {    
        // huge stuff goes here
        self.func();
        // huge stuff goes here
    }    
};

template <typename T>
class Derived: public Base<T>{
public:
    void func(){                    // (2)
        std::cout << "Derived::func\n";
    }    
};

int main(){

    std::cout << '\n';

    Derived<int> derived;
    derived.proc();
    Base<int> base;
    base.proc();     

    std::cout << '\n';

}

see live with explicit this parameter

Or if you want to go with runtime polymorphism, you simply use virtual/override.

#include <iostream>

template <typename T>
class Base{
public:
    virtual void func(){                    // (2)
        std::cout << "Base::func\n";
    }    
    void proc()
    {    
        // huge stuff goes here
        func();
        // huge stuff goes here
    }    
};

template <typename T>
class Derived: public Base<T>{
public:
    void func() override {                    // (2)
        std::cout << "Derived::func\n";
    }    
};

int main(){

    std::cout << '\n';

    Derived<int> derived;
    derived.proc();
    Base<int> base;
    base.proc();     

    std::cout << '\n';

}

with runtime polymorphism

Runtime polymorphism takes another indirection by calling the function via a pointer. This takes a minimal amount of additional time. And, that is more important: A virtual function can never be a templated one. So if your design needs templated methods, you have to go with CRTP or explicit this and can add runtime polymorphism on top by e.g. using std::variant & std::visit.

0
463035818_is_not_an_ai On

There is the Template Method Pattern. Note that its name is somewhat unfortunate, the "template" in the name does not refer to c++ templates, but to the more general meaning of the word.

class Base{
public:
    virtual void func(){
        std::cout << "Base::func\n";
    }

    void proc()
    {
        // huge stuff goes here
        func();
        // huge stuff goes here
    }
};

Derived classes override the virtual method func(). proc itself is not virtual, but calls the virtual func which contains the customized code specific to derived classes:

struct Derived1 : Base {
    void func() override { std::cout << "derived1\n"; }
};
struct Derived2 : Base {
    void func() override { std::cout << "derived2\n"; }
};

int main() {
   Derived1 d1;
   d1.proc();  // calls Derived1::func()
   Derived2 d2;
   d2.proc();  // calls Derived2::func()
}

Live Demo

I think it is worthwile to compare this to CRTP as illustrated in this answer. Here, Derived1 and Derived2 do share a common base class, thats not the case with (classical, pre C++23) CRTP. Here the method have to be virtual, which incurs a small cost.