How to write classes with generic functions that accept parameters of different types and inherit from a base abstract class?

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...:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// bastAbstractTemplates.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#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) {
        unsigned char* bytePointer = &bytes[0];
        TStructure structure = reinterpret_cast<TStructure>(bytePointer);
        return structure;
    }

    template <typename TStructure> std::vector<unsigned char> ToBytes(TStructure structure) {
        const std::unique_ptr<unsigned char[]> bytesArray = std::make_unique<unsigned char[]>(reinterpret_cast<unsigned char[sizeof(structure)]>(structure));
        return std::vector<unsigned char>(bytesArray.get(), bytesArray.get() + sizeof(structure));
    }
};

class ObjectTransformer2 : public ObjectTransformerBaseAbstractClass {
    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 TStructure>
void coutStructureBytes(ObjectTransformerBaseAbstractClass *objectTransformerBaseAbstractClass, TStructure structure) {
    auto bytes = objectTransformerBaseAbstractClass->ToBytes<TStructure>(structure);
    for(auto byte : bytes) {
        std::cout << byte << ' ';
    }
    std::cout << std::endl;
}

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

    int integer = 5;
    float someFloat = 9.79;

    coutStructureBytes(&objectTransformer1, integer);
    coutStructureBytes(&objectTransformer2, someFloat);
}


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, help.
Last edited on
The second answer here: https://stackoverflow.com/questions/2354210/can-a-class-member-function-template-be-virtual
Member function templates cannot be declared virtual. This constraint is imposed because the usual implementation of the virtual function call mechanism uses a fixed-size table with one entry per virtual function. However, the number of instantiations of a member function template is not fixed until the entire program has been translated. Hence, supporting virtual member function templates would require support for a whole new kind of mechanism in C++ compilers and linkers. In contrast, the ordinary members of class templates can be virtual because their number is fixed when a class is instantiated
playing around with multiple dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <cstring>
#include <iostream>
#include <vector>

// all the different specializations
class Transformer_Copy;
class Transformer_Zero;

// abstract base class to pass around the different structures
class Wrapper {
public:
  // for every transformer specialization you'll need these two functions
  // Transformer_Copy
  virtual void to_object(std::vector<unsigned char> bytes,
                         Transformer_Copy *transformer) = 0;
  virtual std::vector<unsigned char>
  to_bytes(Transformer_Copy *transformer) = 0;

  // Transformer_Zero
  virtual void to_object(std::vector<unsigned char> bytes,
                         Transformer_Zero *transformer) = 0;
  virtual std::vector<unsigned char>
  to_bytes(Transformer_Zero *transformer) = 0;
};

class Transformer {
public:
  // note that it works with a Wrapper, no templates here
  // all specializations will implement these two functions the same way
  // so using CRTP for that
  virtual void to_object(std::vector<unsigned char> bytes,
                         Wrapper *structure) = 0;
  virtual std::vector<unsigned char> to_bytes(Wrapper *structure) = 0;
};

// CRTP
// call the appropriate function in the Wrapper class according to the template
// parameter
template <class T> class Transformer_Instance : public Transformer {
public:
  virtual void to_object(std::vector<unsigned char> bytes,
                         Wrapper *structure) override {
    structure->to_object(bytes, reinterpret_cast<T *>(this));
  }
  virtual std::vector<unsigned char> to_bytes(Wrapper *structure) override {
    return structure->to_bytes(reinterpret_cast<T *>(this));
  }
};

class Transformer_Copy : public Transformer_Instance<Transformer_Copy> {
public:
  // actual work
  // yes, they are template, but not virtual
  template <class T> std::vector<unsigned char> to_bytes_template(T structure) {
    std::vector<unsigned char> bytes{};
    bytes.resize(sizeof(structure));
    std::memcpy(&bytes[0], &structure, sizeof(structure));
    return bytes;
  }
  template <class T>
  void to_object_template(std::vector<unsigned char> bytes, T &structure) {
    std::memcpy(&structure, &bytes[0], sizeof(structure));
  }
};

// another specialization
class Transformer_Zero : public Transformer_Instance<Transformer_Zero> {
public:
  // actual work
  template <class T> std::vector<unsigned char> to_bytes_template(T structure) {
    std::vector<unsigned char> bytes{};
    bytes.resize(sizeof(structure));
    return bytes;
  }
  template <class T>
  void to_object_template(std::vector<unsigned char> bytes, T &structure) {
    bzero(&structure, sizeof(bytes.size()));
  }
};

// the dispatcher
template <class T> class Wrapper_Instance : public Wrapper {
public:
  T value;
  Wrapper_Instance(T value) : value(value) {}

  // here we deal with the derived class directly, so can call their specific
  // member functions
  // again, note that all specializations have the same code

  // Transformer_Copy
  virtual void to_object(std::vector<unsigned char> bytes,
                         Transformer_Copy *transformer) override {
    transformer->to_object_template(bytes, value);
  }
  virtual std::vector<unsigned char>
  to_bytes(Transformer_Copy *transformer) override {
    return transformer->to_bytes_template(value);
  }

  // Transformer_Zero
  virtual void to_object(std::vector<unsigned char> bytes,
                         Transformer_Zero *transformer) override {
    transformer->to_object_template(bytes, value);
  }
  virtual std::vector<unsigned char>
  to_bytes(Transformer_Zero *transformer) override {
    return transformer->to_bytes_template(value);
  }
};

// usage example
template <typename TStructure>
void cout_bytes(Transformer *transformer, TStructure structure) {
  Wrapper_Instance<TStructure> wrapper(structure); // wrapping
  auto bytes = transformer->to_bytes(&wrapper);
  for (auto byte : bytes) {
    std::cout << (int)byte << ' ';
  }
  std::cout << std::endl;
}

template <typename TStructure>
void recover(Transformer *transformer, std::vector<unsigned char> v,
             TStructure &structure) {
  Wrapper_Instance<TStructure> wrapper(structure); // wrapping
  transformer->to_object(v, &wrapper);

  structure = wrapper.value;
}

int main() {
  Transformer_Copy transformer_copy{};
  Transformer_Zero transformer_zero{};

  int integer = 5;
  float someFloat = 9.79;
  std::vector<unsigned char> v{215, 163, 28,
                               65}; // the bytes of the float in my case

  std::cout << "Copy\n";
  cout_bytes(&transformer_copy, integer);
  cout_bytes(&transformer_copy, someFloat);

  recover(&transformer_copy, v, integer);
  recover(&transformer_copy, v, someFloat);

  std::cout << integer << '\n' << someFloat << '\n';
  std::cout << "\n\nZero\n";
  cout_bytes(&transformer_zero, integer);
  cout_bytes(&transformer_zero, someFloat);

  recover(&transformer_zero, v, integer);
  recover(&transformer_zero, v, someFloat);

  std::cout << integer << '\n' << someFloat << '\n';
}


for each new Transformer specialization you'll need to do these things:
* forward declare it before Wrapper definition
* add your class to the Wrapper member function declaration (pure virtual)
* create your own to_bytes_template() and to_object_template() that do the actual work
* override in the Wrapper_Instance, calling the template version
macros may help, but still need to touch three classes...
Last edited on
Here's a C# version of the behavior I'm trying to achieve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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:
https://www.modernescpp.com/index.php/c-is-still-lazy


CRTP and Static Polymorphism. Finally, C++ version of the behavior I was trying to achieve:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 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);
}
PS I can barely follow this code myself even though I wrote it, but I guess I'll get used to it.
Last edited on
Topic archived. No new replies allowed.