Reputation: 15
I have a class UISceneParams
and I use it to store some parameters that I want to send over to a UIScene. It contains a unordered_map
of shared_ptr
s to DataParam
instances.
The problem is that my DataParam must use a template and feature multiple data types.
Is there any way I can make the unordered_map
use multiple data types of DataParams?
I currently call this like this:
UISceneParams p;
p.AddParam<int>("test_value", 123);
p.AddParam<int>("test_value2", 456);
and in my scene I get the parameters using the GetParam<T>
method, like this:
int value1 = p.GetParam<int>("test_value", 0);
int value2 = p.GetParam<int>("test_value2", 0);
I've been trying many ways to circumvent the errors, but I am a beginner with templates in C++. Can someone get me on the right track?
UISceneParams.h
#pragma once
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;
template <typename T>
class DataParam {
public:
DataParam(string t, T v) {
_tag = t;
_val = v;
}
T Value() const {
return _val;
}
string Tag() const {
return _tag;
}
private:
string _tag;
T _val;
};
class UISceneParams {
public:
bool HasParam(string tag) {
return params.find(tag) != params.end();
}
template <typename T>
void AddParam(string tag, T value) {
if (HasParam(tag)) params.erase(tag);
params.insert({ tag, make_shared<DataParam>(tag, value) });
}
template <typename T>
T GetParam(string tag, T def_value) {
if (!HasParam(tag)) return def_value;
auto p = params.find(tag)->second;
return p->Value;
}
private:
unordered_map<string, shared_ptr<DataParam>> params;
};
Edit: Final code
#pragma once
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;
class IDataParam
{
public:
virtual ~IDataParam() = default;
};
template <typename T>
class DataParam : public IDataParam {
public:
DataParam(string t, T v) {
_tag = t;
_val = v;
}
T Value() const {
return _val;
}
string Tag() const {
return _tag;
}
private:
string _tag;
T _val;
};
class UISceneParams {
public:
bool HasParam(string tag) {
return params.find(tag.data()) != params.end();
}
template <typename T>
void AddParam(string tag, T value) {
if (HasParam(tag)) params.erase(tag);
params.insert({ tag, make_shared<DataParam<T>>(tag, value) });
}
template <typename T>
T GetParam(string tag, T def_value) {
if (!HasParam(tag)) return def_value;
auto p = dynamic_cast<const DataParam<T> &>(*params.find(tag)->second);
return p.Value();
}
private:
unordered_map<string, shared_ptr<IDataParam>> params;
};
Upvotes: 1
Views: 127
Reputation: 157
You could make your DataParam
inherit from a marker interface like IDataParam
, where the interface is just like
class IDataParam
{
virtual ~IDataParam() = default;
};
You could then store all your params as unordered_map<string, shared_ptr<IDataParam>>
since they all have the same base type.
Then when getting the param, you just get the IDataParam first by the string key. And then you do a static_cast<const DataParam<T> &>(*gotParam)
for example to cast it down to the correct templated DataParam type before returning.
Of course this would not work if you have multiple params with the same key but different T, of you might also run into trouble if you get a param as a type that it is not, to circumvent this issue you could use dynamic_cast though
Upvotes: 3