Search code examples
c++templatesvirtual

How to derive a template function? OR What is preffered approach for this scenerio?


I've two functions

MultiplyVerison1(T x, T y); // in class A
MultiplyVersion1(T x, T y); // in class B

Above functions are in separate non-template classes.

Now, as part of refactoring I'm trying to create a base class of A and B and creating a pure virtual MultiplyVersion1 but a templte function cannot be marked virtual.

So, how can we achieve the same with template functions?


Solution

  • You can't. There's no way to call a function template in a derived class through a pointer-to-base, that's what "function templates cannot be virtual" means.

    You can think of this as being because it's the call that triggers the instantiation of the function template with a particular type T -- if you call it with int, but the dynamic type of the object you're calling it on isn't known until runtime (whether it's A or B or something else), then there's no way for the compiler to know that it needs to instantiate A::MultiplyVersion1<int> or B::MultiplyVersion1<int> or something else. Actually there's more to it than that, but I think that's enough.

    You can bodge around particular cases, but you won't get the full effect of a virtual function. Something like:

    struct Base {
        template <typename T>
        void MultiplyVersion1(const T &x, const T &y) {
            A *athis = dynamic_cast<A*>(this);
            if (athis) {
                athis->MultiplyVersion1(x,y);
            } else {
                B *bthis = dynamic_cast<B*>(this);
                if (bthis) {
                    bthis->MultiplyVersion1(x,y);
                } else {
                    throw std::logic_error();
                }
            }
        }
        virtual ~Base() {}
    };
    

    Now when you call MultiplyVersion1<int> via a pointer-to-base, both A::MultiplyVersion1<int> and B::MutiplyVersion1<int> are instantiated. But of course you can't easily add new derived classes, which is a serious restriction.

    You could also re-consider whether you really need dynamic polymorphism at all, but that depends entirely on how you're planning to use that base class. You seem to have done OK without it so far.

    If all you want from the base class is code re-use for some other functions, then you don't need dynamic polymorphism. Leave MultiplyVersion1 out of the base class entirely (and maybe don't inherit publicly from the Base, instead inherit privately and bring in the functions you want to re-use with using statements). If the functions you want to define for re-use call MultiplyVersion1, then consider simulated dynamic binding via CRTP:

    #include <iostream>
    
    template <typename Derived>
    struct Base {
        template <typename T>
        void MultiplyVersion2(const T &x, const T &y) {
            static_cast<Derived&>(*this).MultiplyVersion1(x + 1, y + 1);
        }
    };
    
    struct A : private Base<A> {
        friend class Base;
        template <typename T> void MultiplyVersion1(T x, T y) {
            std::cout << x*y << "\n"; 
        }
        using Base::MultiplyVersion2;
    };
    
    struct B : private Base<B> {
        friend class Base;
        template <typename T> void MultiplyVersion1(T x, T y) {
            std::cout << x << " * " << y << " = " << x*y << "\n"; 
        }
        using Base::MultiplyVersion2;
    };
    
    int main() {
        A a;
        a.MultiplyVersion2(1,2);
        B b;
        b.MultiplyVersion2(1,2);
    }