Map to store templated base class.

For my project I have created a templated base class that allows me to store char, int or doubles and from it I can create derived classes that do what I expect.

For example I can do the following :

Data<int> m_Int ;
m_Int.createBuffer(x,y) ;
m_Int.addValue(123) ;
m_Int.addValue(456) ;

Data<char> m_Char ;
m_Char.createBuffer(a, b) ;
m_Char.addValue(“Foo”) ;
m_Char.addValue(“Bar”) ;

However my issue is that I want to create a new class that contains a std map of these base classes so as to allow me to store in the map entries of any and all types. But due to my lack of knowledge regarding templates and specialisation (and maybe C++) I can’t work out how to do this.

myMap.insert(pair< size_t, BaseClass >(1, m_Int)) ;
myMap.insert(pair< size_t, BaseClass >(2, m_Char)) ;


If BaseClass is a template you cannot use it whitout specializing, i.e. providing the template parameter.

Further more this way you would store copies of the base class only and all information regarding the derived class are lost. So you need to use [smart] pointer in order to keep the derived classes.
Hey thanks for the reply, but this is where my inexperience shows. From my understanding I assume you mean something like:

1
2
myMap.insert(pair< size_t, BaseClass < int > >(1, m_Int)) ;
myMap.insert(pair< size_t, BaseClass < char > >(2, m_Char)) ;

OK, I may be barking up the wrong tree, and quite possibly the wrong woodland, but assuming the following is even legitimate how do I define the map so that it will store any of the Data class types: Data<int>, Data<char>, Data<double> As this evidently doesn't work without the declaration of T.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef __DATAMAP_HPP_
#define __DATAMAP_HPP_

#include <map>
#include <memory>
#include <typeinfo>
#include "Data.hpp"

class DataMap {
public:
	DataMap() { }
	virtual ~DataMap() { } ;
	template <typename T>
	void addItem(std::shared_ptr< Data < T > > archive) { 
		m_DataMap.insert(std::pair < std::size_t, std::shared_ptr < Data < T > > >(m_DataMap.size(), archive)) ;
	};
	template <typename T>
	typename std::map< std::size_t, std::shared_ptr< Data < T > > >::iterator getItem(const unsigned int id) { return m_DataMap.find(id) ; };

private:
	std::map < std::size_t, std::shared_ptr< Data < T > > > m_DataMap ;
};

#endif 

BaseClass < int > and BaseClass < char > are different types. You cannot store different types in a map or other standard container.

I would recommend that you create a base class without template which you can use as the type to point to. This could be a complete empty class/struct or it may have functions that do not rely on the template types.

Line 15 could be simplier: m_DataMap[m_DataMap.size()] = archive;

For the getItem(...) function i would recommend that you return the shared_ptr instead of the iterator. An null pointer shows that the element could not be found.
What you need to do is known as type-erasure i.e get rid of type parameters in the map and instead store it is as pointers to a base class of Data that can be dynamic_cast to the actual type of Data at run-time. Why do you need to this? Because the type of DataMap cannot be well-defined if it changes according to the type of Data which is outside DataMap's control

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
class Data_base
{
	public:
		virtual ~Data_base() {};
		template <typename T> <return-type> createBuffer (T t1, T t2) const;//to be implemented after Data, note: const qualification;
		template <typename T> <return-type> addValue (T t);//to be implemented after Data, decide if const qualification needed; 
}
template <typename T>
class Data : public Data_base
{
	public:
		Parameter() {}
		<return-type> createBuffer (m_data1, m_data2) const {} // assuming both arguments are class members;
		<return-type> addValue (...)//consider passing by reference and const qualifier for both methods if suitable; 
	private:	
		T m_data1;
		T m_data2; 
		...
}
template<class T> <return-type> Data_base::createBuffer(t t1, T t2) const
{ return dynamic_cast<const Data<T>&>(*this).createBuffer (); }
template<class T> <return-type> Data_base::addValue(T t)
{ return dynamic_cast<Data<T>&>(*this).setValue(); }
//need dynamic_case over virtual because we want to reduce type to Data_base

class DataMap
{
	...

	private:
		map<size_t, shared_ptr<Data_base>> m_DataMap;
}// 
so instead of storing Data<T> you create a new type (Data_base) using inheritance and store pointers to it without actually exposing the T - this is known as 'type erasure' and this is a good reference on why this is needed: http://www.artima.com/cppsource/type_erasure.html

So now you can do things like:
1
2
3
4
Data<int> d;
m_DataMap.insert(1, &d);//with suitable getters for DataMap of course; 
m_DataMap[0]->second->setValue(int x);//depending on how the Data ctor is defined this either sets x as the value of d or changes the value of d to x; 
//m_DataMap[0]->second->setValue(char c) - throws bad::cast exception  


ps: since I dont' have the header and implementation of Data I've had to make some working assumptions and you might have to edit my code according to your actual defintions
Last edited on

Thank you again for your replies and to answer your question gunnerfunner as to why I need this I want create a map that contains buffers of data; with the map containing a buffer of a given type at map position X. So once the map is constructed it may contains 2 buffers of chars, 2 of ints and 8 of doubles. This map would then be serialised and sent via TCP to another process where the data is actually used.

The Data class itself is derived from the ABC ArchiveBase both of which I’ve included, I appreciate that the ArchiveBase need not be an ABC but for now this is what I have done, indeed the code may prove to be very naive, so comment and criticism are welcome.

Deleted source.
Last edited on
Sorry, I think you misunderstood. My 'why do you need to do this' was a rhetorical question in the context of why we need to do type-erasure and the following bits of my post tried to show, hopefully, how we could go about implementing type erasure. There are no outstanding queries from my post directed at you as such but thanks for the additional background
Since I see that you have already used boost, consider using a container of boost::variant.
It is essentially a type-safe tagged union; if you're already relying on C++17 features, std::variant is a marginally better option.

You should prefer against using dynamic_cast when possible; boost's variant::apply_visitor() will guarantee type safety (by conveniently substitution-failing if you don't cover all the cases) and work very nicely with the serialization library.
Last edited on
gunnerfunner, in your reply you say "//to be implemented after Data" can you or indeed anyone else explain to me what you actually mean by this.

I also note that no one has commented on the code I submitted so I'm taking this as a compliment, well at least for now.
"//to be implemented after Data" => because the Data_base methods need to see the Data methods of the same name and hence implemented after Data ...
Topic archived. No new replies allowed.