c++c++20c++-conceptsc++-templates

Polymorphism using template and concepts


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

Solution

  • 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.