Click here to Skip to main content
15,883,883 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
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...`:

C++
#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.

What I have tried:

I tried to do it using virtual functions but virtual doesn't work with templates.
Posted
Updated 8-Jul-20 9:31am
v3
Comments
[no name] 4-Jul-20 10:57am    
Have you considered to use maybe auto for the parameters? Looks to be much more easy. Sorry does also not work. Same problem with virtual...

But have a look here, accepted solution. Maybe it helps:
c++ - templates may not be ‘virtual’ - Stack Overflow[^]

You are overcomplicating the base class definition. Try this :
C++
using UCHAR		= unsigned char;
using VUCHAR	= std::vector< UCHAR >;

template< typename T >
class ObjectTransformerBase
{
public:
    virtual T ToStructure( VUCHAR & bytes ) = 0;
    virtual VUCHAR ToBytes( T & structure ) = 0;
};
I decided to write a testing class and a couple of helpers and here are what they look like :
C++
// some data to test with

struct SomeData
{
    int m_this { 101 };
    int m_that { 202 };
    int m_other { 303 };

    void Output( const char * msg )   // change trace to your output method of choice
    {
        trace( "%s\n", msg );
        trace( "m_this  is %6d\n", m_this );
        trace( "m_that  is %6d\n", m_that );
        trace( "m_other is %6d\n", m_other );
    }
};

using SomeDataObjectTransformer	= ObjectTransformerBase< SomeData >;

// helpers

template< typename T >
void CopyToItem( T & item, VUCHAR & vdata, size_t & count )
{
    memcpy( & item, & vdata[ count ], sizeof( T ) );
    count += sizeof( T );
}

template< typename T >
void CopyToVector( VUCHAR & vdata, T & item )
{
    UCHAR * pItem = (UCHAR *) & item;
    for( size_t n = 0; n < sizeof( T ); ++n )
        vdata.push_back( pItem[ n ] );
}

// this is the transformer class for SomeData

class SomeDataTransformer : public SomeDataObjectTransformer
{
public:
    virtual SomeData ToStructure( VUCHAR & vbytes )
    {
        SomeData data;
        size_t count = 0;
        CopyToItem( data.m_this, vbytes, count );
        CopyToItem( data.m_that, vbytes, count );
        CopyToItem( data.m_other, vbytes, count );
        return data;
    }

    virtual VUCHAR ToBytes( SomeData & data )
    {
        VUCHAR vbytes;
        CopyToVector( vbytes, data.m_this );
        CopyToVector( vbytes, data.m_that );
        CopyToVector( vbytes, data.m_other );
        return vbytes;
    }
};
Lastly here's a function to test this.
C++
void TestObjectTransformer()
{
    SomeData data1;
    data1.Output( "data1 contents : " );

    SomeDataTransformer sdt;
    auto vecsdata = sdt.ToBytes( data1 );   // transform data to a vector

    // hack alert - cast the vector to a pointer to integers and offset the values

    int * pInts = (int *) & vecsdata[ 0 ];
    size_t vcount = vecsdata.size() / sizeof( int );
    for( size_t n = 0; n < vcount; ++n )
    {
        pInts[ n ] += 10000;
    }

    // copy the vector to some other data and display it

    SomeData data2 = sdt.ToStructure( vecsdata );
    data2.Output( "data2 contents :" );
}
this is what the output looks like :
data1 contents :
m_this  is    101
m_that  is    202
m_other is    303
data2 contents :
m_this  is  10101
m_that  is  10202
m_other is  10303
 
Share this answer
 
v2
If you do it such manner you need one real class somewhere in your hierarchy, which is the base class of all instance classes for the implementation. Best is, when this class has all interfaces and the all virtual functions.

Use clear and explicit naming conventions, because it can else become a mess.
 
Share this answer
 
Comments
[no name] 4-Jul-20 12:52pm    
I don't think naming conventions will help here...
KulaGGin 8-Jul-20 15:22pm    
I am after having generic functions that work with different types, inheritance, polymorphism and being able to override my generic functions in derived classes. C++, unlike C#, can't do it using normal tools like virtual methods, function templates, abstract classes, interfaces and inheritance. Because you can't mix virtual with templates.
What I was looking for is CRTP and static polymorphism. See my accepted solution with working examples in C# and C++ that do just what I'm after.
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<TStructure>(ICollection<byte> bytes);
        ICollection<byte> ToBytes<TStructure>(TStructure structure);
    };

    class ObjectTransformer1 : IObjectTransformer {

        #region Implementation of IObjectTransformerBaseAbstractClass

        public TStructure ToStructure<TStructure>(ICollection<byte> bytes) {
            throw new NotImplementedException();
        }

        public ICollection<byte> ToBytes<TStructure>(TStructure structure) {
            throw new NotImplementedException();
        }

        #endregion

    }

    class ObjectTransformer2 : IObjectTransformer {

        #region Implementation of IObjectTransformerBaseAbstractClass

        public TStructure ToStructure<TStructure>(ICollection<byte> bytes) {
            throw new NotImplementedException();
        }

        public ICollection<byte> ToBytes<TStructure>(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 <iostream>
#include <vector>
#include <string>

template<typename DerivedClass>
class IObjectTransformer {
public:
    template<typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes);
    template<typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure);
private:
    IObjectTransformer() = default;

    friend DerivedClass;
};

template <typename DerivedClass>
template <typename TStructure>
TStructure IObjectTransformer<DerivedClass>::ToStructure(std::vector<unsigned char> bytes) {
    return static_cast<DerivedClass*>(this)->ToStructure(bytes);
}

template <typename DerivedClass>
template <typename TStructure>
std::vector<unsigned char> IObjectTransformer<DerivedClass>::ToBytes(TStructure structure) {
    return static_cast<DerivedClass*>(this)->ToBytes(structure);
}

class ObjectTransformer1 : public IObjectTransformer<ObjectTransformer1> {
public:
    template <typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) {
        unsigned char* bytePointer = &bytes[0];
        TStructure structure = reinterpret_cast<TStructure>(*bytePointer);
        return structure;
    }

    template <typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure) {
        char* bytesArray = reinterpret_cast<char*>(&structure);
        auto byteVec = std::vector<unsigned char>(bytesArray, bytesArray + sizeof(TStructure));
        return byteVec;
    }
};

class ObjectTransformer2 : public IObjectTransformer<ObjectTransformer2> {
public:
    template <typename TStructure> TStructure ToStructure(std::vector<unsigned char> bytes) {
        TStructure structure{};
        std::memcpy(&structure, &bytes[0], sizeof(TStructure));
        return structure;
    }
    template <typename TStructure>
    std::vector<unsigned char> ToBytes(TStructure structure) {
        std::vector<unsigned char> bytes{};
        bytes.resize(sizeof(TStructure));
        std::memcpy(&bytes[0], &structure, sizeof(TStructure));
        return bytes;
    }
};


template <typename DerivedClass, typename TStructure>
void CoutStructureBytes(IObjectTransformer<DerivedClass> *objectTransformerBaseAbstractClass, TStructure structure) {
    auto bytes = objectTransformerBaseAbstractClass->template ToBytes<TStructure>(structure);
    for(auto byte : bytes) {
        std::cout << std::to_string(byte) << ' ';
    }
    std::cout << std::endl;
}

int main() {
    ObjectTransformer1 objectTransformer1{};
    ObjectTransformer1 objectTransformer2{};

    int integer = 5;
    float someFloat = 9.79f;

    CoutStructureBytes(&objectTransformer1, integer);
    CoutStructureBytes(&objectTransformer2, someFloat);
}
PS I can barely follow this code myself even though I wrote it, but I guess I'll get used to it.
 
Share this answer
 
v3

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900