-1

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.

KulaGGin
  • 943
  • 2
  • 12
  • 27
  • "that accept parameters of different types" vs "without using templates".??? – Klaus Jul 04 '20 at 10:15
  • Hint: Please simlify your code to an example which ONLY shows where the problem is. All the stuff inside your example methods is really only wasting readers time. A simple print would be more than sufficient! Please remove all the memcpy/resize,bla stuff, as it has really nothing to do with the question of an abstract base class with unknown parameter types... – Klaus Jul 04 '20 at 10:23
  • @Klaus Ok, I simplified the example to only base and derived classes and didn't include implementations. – KulaGGin Jul 04 '20 at 10:37

2 Answers2

0

You are trying to use method templates which are declared virtual. That is not possible. See also this answer to a related question.

It appears that you are trying to create some sort of serialization/deserialization mechanism. You want to convert different structures (implemented in different classes) to bytes, and want to be able to recreate an object of the correct type by interpreting the bytes in some way.

Let's first handle the serialization issue. You could solve that easily by defining an interface for all structures with a serialization method every structure should implement:

class Structure
{
public:
    virtual std::vector<uint8_t> ToBytes() = 0;

    // any other functionality common to all structures
};

A particular structure would then look like this:

class SomeStructure: public Structure
{
public:
    SomeStructure(std::vector<uint8_t> bytes)
    {
        // deserialize (create object from bytes)
    }

    std::vector<uint8_t> ToBytes() override
    {
        // serialize
    }

    // explained below
    static constexpr uint8_t TypeId()
    {
        return 42;
    }

    // any specific methods for the structure
};

Now the deserialization problem. You need to have some way of identifying the class type in your byte stream. Let's suppose your structure always writes a unique class ID as the first byte. You could then use a factory which creates the correct object by identifying the type:

class StructureBuilder
{
public:
    virtual Structure* FromBytes(std::vector<uint8_t> bytes) = 0;
    virtual uint8_t TypeId() = 0;
};

class StructureFactory
{
public:
    static StructureFactory& Instance()
    {
        // usual singleton stuff: create if not existing yet, then return instance
    }

    Structure* FromBytes(std::vector<uint8_t> bytes)
    {
        for (auto& builder : builders)
        {
            if (builder->TypeId() == bytes[0])
                return builder->BuildStructureFromBytes(bytes);
                
            return nullptr;
        }
    }

    void Add(StructureBuilder* builder)
    {
        // add to list
    }

    void Remove(StructureBuilder* builder)
    {
        // remove from list
    }

private:
    StructureFactory()
    {
    }
    std::list<StructureBuilder*> builders;
    static StructureFactory* instance;
};

The builders would all look very similar, therefore you could utilize a template here to make it a bit easier to create one for every structure:

template <typename T>
class TypedStructureBuilder: public StructureBuilder
{
public:
    TypedStructureBuilder()
    {
        StructureFactory::Instance().Add(this);
    }

    virtual ~TypedStructureBuilder()
    {
        StructureFactory::Instance().Remove(this);
    }

    Structure* BuildStructureFromBytes(std::vector<uint8_t> bytes) override
    {
        return new T(bytes);
    }

    uint8_t TypeId() override
    {
        return T::TypeId();
    }
};

Then all you need to do to register some new structure at the factory, is to have this somewhere (I would put it in SomeStructure.cpp):

static TypedStructureBuilder<SomeStructure> builder;

Hope this helps.

  • I understand it is not possible to use templates and virtual functions at the same time. That is what I attempted to do to fix my problem and failed. I'm sure that there is a solution to my problem and it is possible to solve my problem, I just can't use templates and virtual functions at the same time in this case. – KulaGGin Jul 04 '20 at 11:03
  • I looked a bit closer into what you are trying to achieve and edited my answer to come up with a possible solution. – Daniël Schenk Jul 04 '20 at 12:10
  • Thank you for going out of your way and writing this example. Though, my serialization example is just an example. I am after achieving polymorphism and inheritance with templated methods to reduce duplication of code, and the serialization example was just an example. See my answer how I achieved polymorphism and inheritance with generic templated functions: stackoverflow.com/a/62802439/6693304 – KulaGGin Jul 08 '20 at 19:48
0

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);
}
KulaGGin
  • 943
  • 2
  • 12
  • 27