Search code examples
c++abstract-classfunction-templates

How to achieve polymorphism with templated functions?


In my project, I have a base abstract class with an interface, which derived classes implement. These derived classes have generic functions that accept parameters of different types. I have written these generic functions in my derived classes using function templates.

I want to add these templated functions to the interface in my base class. So I can achieve polymorphism: accept base class in other functions and call these templated functions in derived classes.

When we have normal functions, we do virtual and override, but you can't do virtual with templated functions.

I tried to do pure abstract templated functions in my abstract base class but it doesn't work.

Here's a small program with the functionality I'm trying to achieve, which doesn't compile because of virtual <template...:

#include <vector>

class ObjectTransformerBaseAbstractClass {
public:
    virtual template<typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) = 0;
    virtual template<typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure) = 0;
};

class ObjectTransformer1 : public ObjectTransformerBaseAbstractClass {
    template <typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) {
        // some implementation
    }

    template <typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure) {
        // some implementation
    }
};

class ObjectTransformer2 : public ObjectTransformerBaseAbstractClass {
    template <typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) {
        // some other implementation
    }
    template <typename TStructure>
    std::vector<unsigned char> ToBytes(TStructure structure) {
        // some other implementation
    }
};

template <typename TStructure>
void coutStructureBytes(ObjectTransformerBaseAbstractClass *objectTransformerBaseAbstractClass, TStructure structure) {
    // transform structure to bytes using the passed objectTransformerBaseAbstractClass object and cout it.
}

In my base class I need to say "Implement these pure abstract generic functions which accept different parameters of different types and do stuff in derived classes". And in my derived classes I need to implement these pure abstract generic functions that accept parameters of different types.

I don't understand how to achieve this functionality I want to have(which you can see in the above program, if it compiled and worked). Please, recommend a solution or explain how to make this work.


Solution

  • Here's a C# version of the behavior I'm trying to achieve:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace baseAbstractTemplates.NET {
        interface IObjectTransformer {
            TStructure ToStructure&lt;TStructure&gt;(ICollection&lt;byte&gt; bytes);
            ICollection&lt;byte&gt; ToBytes&lt;TStructure&gt;(TStructure structure);
        };
    
        class ObjectTransformer1 : IObjectTransformer {
    
            #region Implementation of IObjectTransformerBaseAbstractClass
    
            public TStructure ToStructure&lt;TStructure&gt;(ICollection&lt;byte&gt; bytes) {
                throw new NotImplementedException();
            }
    
            public ICollection&lt;byte&gt; ToBytes&lt;TStructure&gt;(TStructure structure) {
                throw new NotImplementedException();
            }
    
            #endregion
    
        }
    
        class ObjectTransformer2 : IObjectTransformer {
    
            #region Implementation of IObjectTransformerBaseAbstractClass
    
            public TStructure ToStructure&lt;TStructure&gt;(ICollection&lt;byte&gt; bytes) {
                throw new NotImplementedException();
            }
    
            public ICollection&lt;byte&gt; ToBytes&lt;TStructure&gt;(TStructure structure) {
                throw new NotImplementedException();
            }
    
            #endregion
    
        }
    
        class Program {
            public static void CoutStructureBytes(IObjectTransformer objectTransformer) {
                var bytes = objectTransformer.ToBytes(3);
                Console.WriteLine(bytes);
            }
    
            static void Main(string[] args) {
                ObjectTransformer1 objectTransformer1 = new ObjectTransformer1();
                ObjectTransformer2 objectTransformer2 = new ObjectTransformer2();
                CoutStructureBytes(objectTransformer1);
                CoutStructureBytes(objectTransformer2);
            }
        }
    }
    

    In C# it just works in "haha C# interfaces, templates, polymorphism go brrr" style. Even if you're not familiar with C# at all but have C++ knowledge, I'm sure you can follow that C# code just fine.

    This compiles and runs just fine, throws NotImplementedException because not implemented.

    But in C++, unlike in C#, I can't just have interfaces with templates, inheritance and polymorphism using usual tools: pure abstract functions(which I override in derived classes), templates, inheritance and method overriding. Because I can't mix method templates with virtual.

    After a few days of research I finally found how it's done here: C++ is Lazy: CRTP - ModernesCpp.com[^]

    CRTP and Static Polymorphism. Finally, C++ version of the behavior I was trying to achieve:

    // bastAbstractTemplates.cpp : This file contains the 'main' function. Program execution begins and ends there.
    //
    
    #include &lt;iostream&gt;
    #include &lt;vector&gt;
    #include &lt;string&gt;
    
    template&lt;typename DerivedClass&gt;
    class IObjectTransformer {
    public:
        template&lt;typename TStructure&gt; TStructure ToStructure(std::vector&lt;unsigned char&gt; bytes);
        template&lt;typename TStructure&gt; std::vector&lt;unsigned char&gt; ToBytes(TStructure structure);
    private:
        IObjectTransformer() = default;
    
        friend DerivedClass;
    };
    
    template &lt;typename DerivedClass&gt;
    template &lt;typename TStructure&gt;
    TStructure IObjectTransformer&lt;DerivedClass&gt;::ToStructure(std::vector&lt;unsigned char&gt; bytes) {
        return static_cast&lt;DerivedClass*&gt;(this)-&gt;ToStructure(bytes);
    }
    
    template &lt;typename DerivedClass&gt;
    template &lt;typename TStructure&gt;
    std::vector&lt;unsigned char&gt; IObjectTransformer&lt;DerivedClass&gt;::ToBytes(TStructure structure) {
        return static_cast&lt;DerivedClass*&gt;(this)-&gt;ToBytes(structure);
    }
    
    class ObjectTransformer1 : public IObjectTransformer&lt;ObjectTransformer1&gt; {
    public:
        template &lt;typename TStructure&gt; TStructure ToStructure(std::vector&lt;unsigned char&gt; bytes) {
            unsigned char* bytePointer = &amp;bytes[0];
            TStructure structure = reinterpret_cast&lt;TStructure&gt;(*bytePointer);
            return structure;
        }
    
        template &lt;typename TStructure&gt; std::vector&lt;unsigned char&gt; ToBytes(TStructure structure) {
            char* bytesArray = reinterpret_cast&lt;char*&gt;(&amp;structure);
            auto byteVec = std::vector&lt;unsigned char&gt;(bytesArray, bytesArray + sizeof(TStructure));
            return byteVec;
        }
    };
    
    class ObjectTransformer2 : public IObjectTransformer&lt;ObjectTransformer2&gt; {
    public:
        template &lt;typename TStructure&gt; TStructure ToStructure(std::vector&lt;unsigned char&gt; bytes) {
            TStructure structure{};
            std::memcpy(&amp;structure, &amp;bytes[0], sizeof(TStructure));
            return structure;
        }
        template &lt;typename TStructure&gt;
        std::vector&lt;unsigned char&gt; ToBytes(TStructure structure) {
            std::vector&lt;unsigned char&gt; bytes{};
            bytes.resize(sizeof(TStructure));
            std::memcpy(&amp;bytes[0], &amp;structure, sizeof(TStructure));
            return bytes;
        }
    };
    
    
    template &lt;typename DerivedClass, typename TStructure&gt;
    void CoutStructureBytes(IObjectTransformer&lt;DerivedClass&gt; *objectTransformerBaseAbstractClass, TStructure structure) {
        auto bytes = objectTransformerBaseAbstractClass-&gt;template ToBytes&lt;TStructure&gt;(structure);
        for(auto byte : bytes) {
            std::cout &lt;&lt; std::to_string(byte) &lt;&lt; ' ';
        }
        std::cout &lt;&lt; std::endl;
    }
    
    int main() {
        ObjectTransformer1 objectTransformer1{};
        ObjectTransformer1 objectTransformer2{};
    
        int integer = 5;
        float someFloat = 9.79f;
    
        CoutStructureBytes(&amp;objectTransformer1, integer);
        CoutStructureBytes(&amp;objectTransformer2, someFloat);
    }