Storing template classes in STL containers is tricky 🙂 If v is a std::vector and TC is a template class, how can we do the following:
1 2 3 4 5 6 |
v.push_back(new TC<char>('X')); v.push_back(new TC<int>(1)); v.push_back(new TC<float>(3.141)); for(auto it : v) it->doWork(); |
The trick is two-fold: 1) we need a non-template base class (let’s call it NTB) and 2) we need to store pointers to the non-template base class in the STL container. Like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class NTB { public: virtual ~NTB() {} virtual void doWork() = 0; }; template<typename T> class TC : public NTB { public: TC(T t) : m_T(t) {} virtual void doWork() override { cout << m_T << endl; } private: T m_T; }; |
Complete listing:
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 |
#include <iostream> #include <vector> using namespace std; class NTB { public: virtual ~NTB() {} virtual void doWork() = 0; }; template<typename T> class TC : public NTB { public: TC(T t) : m_T(t) {} virtual void doWork() override { cout << m_T << endl; } private: T m_T; }; int main(int argc, char** argv) { vector<NTB*> v; v.push_back(new TC<char>('X')); v.push_back(new TC<int>(1)); v.push_back(new TC<float>(3.141)); for(auto it : v) it->doWork(); for(auto it : v) delete it; return 1; } |
There is a memory leak in your “trick”
==5022== HEAP SUMMARY:
==5022== in use at exit: 48 bytes in 3 blocks
==5022== total heap usage: 7 allocs, 4 frees, 72,808 bytes allocated
==5022==
==5022== LEAK SUMMARY:
==5022== definitely lost: 48 bytes in 3 blocks
yes I know. it is an example of how to store T’s in containers. a user can figure out that the content of the vector needs deleting 😉
better now? 😉
This is what I like to call the heterogeneous container problem. If you use templates enough, you’re sure to run into it! In the past I have solved it in one of two ways:
1) vector of variants
using variant_type = std::variant<TC, TC, TC>;
using vector_type = std::vector;
vector_type v;
v.push_back(TC(‘x’));
v.push_back(TC(1));
v.push_back(TC(3.14));
for (auto& it : v) {
std::visit(v, [](auto& item) {
item.doWork();
});
}
v.clear();
2) “sparse” (per-type) collections
If you don’t truly need each type to be in the same list, you can make each class maintain it’s own list. This works better for things like queuing async operations. You can also combine it with static dispatch.
template
class TC {
public:
TC(T t) : m_T(t) {}
void doWork() { cout << m_T << endl; }
private:
static std::vector<TC> m_v;
T m_T;
};
TC::queue(TC(‘x’));
TC::queue(TC(1));
TC::queue(TC(3.14));
std::array<std::function<void()>, 3> qs {
&TC::completeAllWork,
&TC::completeAllWork,
&TC::complateAllWork
};
for (std::size_t i=0; i<qs.size(); i++) {
qsi;
}
For inline code use back ticks ‘`’ and for clode blocks use pre tag “< pre >” and “< / pre >” 🙂