C++ std::map holding ANY type of value
Blindy's answer is very good (+1), but just to complete the answer: there is another way to do it with no library, by using dynamic inheritance:
class MyFieldInterface
{
int m_Size; // of course use appropriate access level in the real code...
~MyFieldInterface() = default;
}
template <typename T>
class MyField : public MyFieldInterface {
T m_Value;
}
struct MyClass {
std::map<string, MyFieldInterface* > fields;
}
Pros:
- it's familiar to any C++ coder
- it don't force you to use Boost (in some contexts you are not allowed to);
Cons:
- you have to allocate the objects on the heap/free store and use reference semantic instead of value semantic to manipulate them;
- public inheritance exposed that way might lead to over-use of dynamic inheritance and a lot of long-term issues related to your types really being too inter-dependent;
- a vector of pointers is problematic if it have to own the objects, as you have to manage destruction;
So use boost::any or boost::variant as default if you can, and consider this option only otherwise.
To fix that last cons point you could use smart pointers:
struct MyClass {
std::map<string, std::unique_ptr<MyFieldInterface> > fields; // or shared_ptr<> if you are sharing ownership
}
However there is still a potentially more problematic point:
It forces you to create the objects using new/delete (or make_unique/shared). This mean that the actual objects are created in the free store (the heap) at any location provided by the allocator (mostly the default one). Therefore, going though the list of objects very often is not as fast as it could be because of cache misses.
If you are concerned with performance of looping through this list very often as fast as possible (ignore the following if not), then you'd better use either boost::variant (if you already know all the concrete types you will use) OR use some kind of type-erased polymorphic container.
The idea is that the container would manage arrays of objects of the same type, but that still expose the same interface. That interface can be either a concept (using duck-typing techniques) or a dynamic interface (a base class like in my first example).
The advantage is that the container will keep same-type objects in separate vectors, so going through them is fast. Only going from one type to another is not.
Here is an example (the images are from there): http://bannalia.blogspot.fr/2014/05/fast-polymorphic-collections.html
However, this technique loose it's interest if you need to keep the order in which the objects are inserted.
In any way, there are several solutions possible, which depends a lot on your needs. If you have not enough experience with your case, I suggest using either the simple solution I first explained in my example or boost::any/variant.
As a complement to this answer, I want to point very good blog articles which summarize all C++ type-erasure techniques you could use, with comments and pros/cons:
- http://talesofcpp.fusionfenix.com/post-16/episode-nine-erasing-the-concrete
- http://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/
- http://akrzemi1.wordpress.com/2013/12/06/type-erasure-part-ii/
- http://akrzemi1.wordpress.com/2013/12/11/type-erasure-part-iii/
- http://akrzemi1.wordpress.com/2014/01/13/type-erasure-part-iv/
std::map holding any value type
You can't use a run-time parameter to choose between multiple types. You need to decide what type is returned at compile time. But this is exactly what templates are for, so you could simply do:
template <typename T>
MyField<T>* getType(T val) {
return new MyField<T>(val);
}
and then use the function like this:
obj.fields["a"] = obj.getType(1);
obj.fields["b"] = obj.getType(1.1);
obj.fields["c"] = obj.getType('a');
Note that you don't need to specify T
at the call site, since that can be deduced from the function argument.
Of course, you will need to keep track of what type each key in the map refers to separately.
Here's a demo.
Also, the way this function is implemented, the name getType
is confusing, since you are not actually returning a type here. Perhaps a better name would be getAnyPointer
.
A map in c++ which can accept any type of value
As the previous answer correctly suggested, you can't do it out of the box in C++. I am assuming that by "[...] which can accept any type of value [...]" you mean the value, not the key of the map.
Here is what you can do, though. You have two options; I'll go from ugly to nice.
First approach:
Create a value holding class which you will be using as a
value
for the map. Let's call it Value.Implement explicit constructors in that class for all types you want to support, and keep track on the type of value the class is currently storing
Check the value's type after getting it from the map and use the appropriate getter function
Optionally, overload the
<<
operator to support standard streams
For a sample implementation, see the following code:
#include <iostream>
#include <memory>
#include <map>
class Value {
public:
typedef enum {
String,
Integer,
Double,
Float
} ContentType;
private:
ContentType m_ctType;
std::string m_strContent;
int m_nContent;
double m_dContent;
float m_fContent;
public:
Value() : m_strContent(""), m_ctType(String) {}
explicit Value(const char* arrcString) : m_strContent(std::string(arrcString)), m_ctType(String) {}
explicit Value(std::string strContent) : m_strContent(strContent), m_ctType(String) {}
explicit Value(int nContent) : m_nContent(nContent), m_ctType(Integer) {}
explicit Value(double dContent) : m_dContent(dContent), m_ctType(Double) {}
explicit Value(float fContent) : m_fContent(fContent), m_ctType(Float) {}
~Value() {}
ContentType type() {
return m_ctType;
}
std::string stringValue() { return m_strContent; }
int integerValue() { return m_nContent; }
double doubleValue() { return m_dContent; }
float floatValue() { return m_fContent; }
};
std::ostream& operator<<(std::ostream& osStream, Value& valOut) {
switch(valOut.type()) {
case Value::String: osStream << valOut.stringValue(); break;
case Value::Integer: osStream << valOut.integerValue(); break;
case Value::Double: osStream << valOut.doubleValue(); break;
case Value::Float: osStream << valOut.floatValue(); break;
}
return osStream;
}
This is usable like so:
int main() {
std::map<int, Value> mapAnyValue;
mapAnyValue[0] = Value("Test");
mapAnyValue[1] = Value(1337);
std::cout << mapAnyValue[0] << ", " << mapAnyValue[1] << std::endl;
return 0;
}
This outputs
Test, 1337
Now some might argue that this is
- Inefficient (it reserved fields for types that are not used in every
Value
instance) - Difficult to extend/maintain (adding new fields is kind of cumbersome)
- In general bad design
and they are right. So here is an alternative using polymorphism and templates.
Second approach:
This requires you to define the type of value you want to store when assigning it to a variable, and it requires the use of pointers. The reasons are given below.
For this approach, we do the following:
Create a base class
ValueBase
that serves as a class we can put in our map as value type.Derive from this class a templated class
Value<T>
that holds an arbitrary value of template typeT
.To support
std::cout
and friends, we implement an operator overloading for<<
for classValueBase
, add a pure virtualoutput
function toValueBase
, and override this function inValue<T>
to make use of the default<<
operator for any type you are using in the template.
See below for a code sample:
#include <iostream>
#include <memory>
#include <map>
class ValueBase {
public:
ValueBase() {}
~ValueBase() {}
virtual void output(std::ostream& osStream) = 0;
};
template<typename T>
class Value : public ValueBase {
private:
T m_tValue;
public:
Value(T tValue) : m_tValue(tValue) {}
~Value() {}
T value() {
return m_tValue;
}
void output(std::ostream& osStream) override {
osStream << m_tValue;
}
};
std::ostream& operator<<(std::ostream& osStream, ValueBase& valOut) {
valOut.output(osStream);
return osStream;
}
This is usable like so:
int main() {
std::map<int, std::shared_ptr<ValueBase>> mapAnyValue;
mapAnyValue[0] = std::make_shared<Value<std::string>>("Test");
mapAnyValue[1] = std::make_shared<Value<int>>(1337);
std::cout << *mapAnyValue[0] << ", " << *mapAnyValue[1] << std::endl;
return 0;
}
Or without smart pointers:
int main() {
std::map<int, ValueBase*> mapAnyValue;
mapAnyValue[0] = new Value<std::string>("Test");
mapAnyValue[1] = new Value<int>(1337);
std::cout << *mapAnyValue[0] << ", " << *mapAnyValue[1] << std::endl;
delete mapAnyValue[0];
delete mapAnyValue[1];
return 0;
}
Both output
Test, 1337
There are a couple of differences in terms of usage for the second approach.
First of all, you need to use pointers. The reason for this is that this way, the member function vtable is preserved and you can override functions in the base class from derived classes. In our situation, this means: When we call output()
on a pointer of type ValueBase
that was initialized as a Value<T>
, the output()
function from Value<T>
is used rather than from ValueBase
. If you used normal variables instead of pointers, the output()
function from ValueBase
would be used, and we lose the information from the derived class.
Second, and this is related to the first one, you need to reference the pointer you get when using the value. If you want to output a ValueBase
or Value<T>
pointer with std::cout
, you need to do it as std::cout << *var
to output the contained value. If you just did std::cout << var
, you would correctly get the address of the pointer instead.
I'm sure there are other options, especially when using Boost, but I'm no expert on that. Someone else might have more valuable information on that.
Other than that, what you are doing sounds like an act of laziness. C++ has a strongly typed system for a reason; not only is it well-defined, but you also know what to expect from your code. If you start making things fuzzy and use arbitrary container objects for all kinds of tasks, your code will lose readability, clarity, and will (most probably) produce numerous bugs that are very difficult to trace, debug, and in the end fix, because you need to support all the fancy containers you introduced to keep your framework running.
If you want to use a language like Java, its better to use Java instead of C++.
Is there a way to have a std::map without declare the value type
No, you can't do this straight away. Consider using some type-erasure device, peferably type-safe and well-understood, like boost::variant
or boost::any
. Otherwise, you can craft a value
class that contains an opaque heap-allocated buffer (an old-school void*
) and you are supposed to cast it to a type depending on the value of an enum
field.
Why can templates only be implemented in the header file?
Caveat: It is not necessary to put the implementation in the header file, see the alternative solution at the end of this answer.
Anyway, the reason your code is failing is that, when instantiating a template, the compiler creates a new class with the given template argument. For example:
template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};
// somewhere in a .cpp
Foo<int> f;
When reading this line, the compiler will create a new class (let's call it FooInt
), which is equivalent to the following:
struct FooInt
{
int bar;
void doSomething(int param) {/* do stuff using int */}
}
Consequently, the compiler needs to have access to the implementation of the methods, to instantiate them with the template argument (in this case int
). If these implementations were not in the header, they wouldn't be accessible, and therefore the compiler wouldn't be able to instantiate the template.
A common solution to this is to write the template declaration in a header file, then implement the class in an implementation file (for example .tpp), and include this implementation file at the end of the header.
Foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
This way, implementation is still separated from declaration, but is accessible to the compiler.
Alternative solution
Another solution is to keep the implementation separated, and explicitly instantiate all the template instances you'll need:
Foo.h
// no implementation
template <typename T> struct Foo { ... };
Foo.cpp
// implementation of Foo's methods
// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float
If my explanation isn't clear enough, you can have a look at the C++ Super-FAQ on this subject.
Related Topics
Qgraphicsview Zooming in and Out Under Mouse Position Using Mouse Wheel
How to Get the Class Name from a C++ Object
Error: Use of Deleted Function
Linking Different Libraries for Debug and Release Builds in Cmake on Windows
Problems with Singleton Pattern
Floating Point Comparison Revisited
Pass by Value VS Pass by Rvalue Reference
Does C++ Pass Objects by Value or Reference
C++ Std::Map Holding Any Type of Value
"Dereferencing Type-Punned Pointer Will Break Strict-Aliasing Rules" Warning