writeln(is(Tuple!(string, int) == struct)); // true
What is real user case when I should use Tuple instead of struct?
Tuple is mostly for convenience as it's often shorter to write tuple(0, "bar") than defining the struct.
There are a few use cases where a tuple is handy, e.g. unwrapping to an AliasSeq:
import std.typecons : tuple;
void bar(int i, string s) {}
void main()
{
auto t = tuple(1, "s");
bar(t.expand);
}
expand can also be handy when working with ranges:
void main()
{
import std.stdio : writeln;
import std.typecons : tuple;
auto ts = tuple(1, "s");
foreach (t; ts)
{
t.writeln;
}
import std.algorithm : minElement;
import std.range;
tuple(2, 1, 3).expand.only.minElement.writeln; // 1
}
Another real-use case is zip where the result of zip([0], ["s"]) is Tuple!(int, string) (or in general staticMap!(ElementType, Args)) which is easier than generating the struct dynamically (though of course with static foreach or mixin that would be possible too).
Largely choosing one over the other is based on preference, some coder may never use Tuples and other most of the time.
They do generally have different use cases.
The primary difference is that a Tuple is typed (named/identified by the compiler) by the types in it while a Struct is typed by a label chosen by the coder. i.e. if a function returns Tuple!(string,int) and another function that returns a string and int tuple will be returning the same type; but if a function returns a struct (e.g. Struct fun(arg)) that contains a string and int (struct Struct{string s; int i;}) it can be returning a different type than another function that returns a different struct with the same types.
Essentially the same difference and reasons a coder would choose a struct or an array for Vector or Position (struct Pos {int x; int y;} vs int[2])
Tuple!(string,int) == Tuple!(string,int)
StructA != StructB
No hard rule just how strongly typed you want to / is appropriate to be.
Related
Iterator of each datastructure has different type of return value of operator*() e.g. :-
Most array-like DataStructure<T> : return T&.
std::unordered_map<K,T> : return std::pair<const K, T>&.
const std::unordered_map<K,T> : return const std::pair<K, T>&.
What should be the datatype if my iterator::operator*() want to return something more complex (std::pair is not enough)?
Example
I have a custom 3D datastructure Map3D<T> that is like a map (int,int,int)->T.
To achieve the above requirement, I create a new class Bundle3D<T>.
template<class T>class Bundle3D {
//getInternalIndex1D()
//getCoordinate()
//getValue()
};
The Map3D<T>'s iterator::operator* would have signature like :-
Bundle3D<T> iterator::operator*(){ ... }
Its usage is like :-
Map3D<float> map;
for(auto& ele:map){ //"ele" has some special type named "Bundle"
int keyInternal1DIndex=ele.getInternalIndex1D();
Vector3Int& key=ele.getCoordinate();
float& value=ele.getValue();
}
It works good, but I think my code is not standardized.
In other words, Bundle3D<T>, getInternalIndex1D(), getCoordinate() and getValue() is blindly named by me.
In real case, I have created a lot of custom data-structures that generate such strange iterator.
Question
Is there any std::/standard type of return-value of iterator::operator*(), when T& and std::pair is not enough?
I have doubted about this for several months, but feel very reluctant to ask it.
If this question need improvement, please comment.
Edit
(clarify the word standardized - this part is my subjective notion.)
I feel that all types of standard collection in most language, e.g. :-
java.util.Collection/ArrayList in Java
std::unordered_map/vector in C++
Iterators of all of them have signatures of function getValue() or operator* that return either T or StandardPair<index,T> - no other types.
For many years, I am convinced that it is a rule or a strong convention/tradition that I really should obey.
There must be a reason.
However, I am not sure what it is, because I am probably very new to C++.
If I design my software to return something strange (e.g. Bundle3D<T> in the above example), I think I will get punished hard from the unconventional design. (e.g. not have .first field)
What you have right now is okay. I would just specify one thing that I think isn't very C++-ish and may in fact harm your standardization options in the future.
It's the getters. Obviously you have deep roots in Java, and the concept of public members is abhorred in Java programming, so much so that the concept of "beans" exists. I don't intend to knock on Java here, it's a nice language with its own nice idioms.
But this is C++, with it's own programming idioms. You obviously noticed the direct access to the contents of std::pair. It's like that for a reason. A pair is just two items packed together, that's the sum of its behavior. Getters would just ,well, get in the way. :)
There's no need for them, because we aren't dealing with an "abstraction" of two items bundled together, but instead we really do have a concrete, honest to god, bundle1.
Obviously we can have bundles of more than two items. That's why we have std::tuple. Now while it's true that all access to a tuple is through a non-member function called get, that's simply because there is no way to give names to members of an arbitrarily sized bundle. get still returns a reference to the element, so you retain direct access into the bundle.
This long winded exposition is important, because an upcoming C++1z feature, called "Structured Bindings", relies on the standard behavior of tuples and pairs, as well as how C++ programmers see aggregates. Under this feature, an iteration over a map, will look like this:
#include <map>
#include <iostream>
std::map<char const*, int> foo()
{
return {
{ "foo", 3 },
{ "bar", 7 },
{ "baz", 1 },
};
}
int main() {
for (auto [key, val] : foo()) {
std:: cout << "( " << key << ", " << val << " )\n";
}
return 0;
}
Live example
And the same extends to any user defined bundle of data. Your own return value type in the post can be bound similarly, if the members are public:
struct Vector3Int {};
template<class T>
struct Bundle3D {
int internal_index_id;
Vector3Int const &coord;
T &value;
};
int main() {
Vector3Int vec;
float val;
Bundle3D<float> bundle{ 1, vec, val };
auto[ idx_id, coord, value] = bundle;
// coord = {}; // compile error since coord gets the cv qualifer
}
Live example
So my suggestion is, leave the getters to your Java code, and use aggregates as bundles of data for C++.
1 An aggregate, to be more formal.
I'm going to assume that your Map3D is a spacial co-ordinate to value container.
I would return a std::pair<const Vector3Int, T>&, and not bother with getInternalIndex1D as part of the return. That can be left as a function Vector3Int -> int.
Have a look at UnorderedAssociativeContainer for the sorts of members that would be useful to define
e.g.
template <typename T>
class Map3D
{
using key_type = Vector3Int;
using mapped_type = T;
using value_type = Bundle3D<T>; // Key, Value, Internal1DIndex
using reference = Bundle3D<T> &;
using pointer = Bundle3D<T> *;
using hasher = /*Function object that calculates Internal1DIndex*/
using key_equal = std::equal<Vector3Int>
... iterators, const_* definitions, etc
}
Consider this code:
template isSend(T){
import std.meta;
import std.traits;
alias fieldNames = FieldNameTuple!T;
static if(fieldNames.length == 1 && fieldNames[0] == ""){
enum isSend = true;
}
else{
alias toType(string s) = typeof(__traits(getMember, T, s));
enum isSend = allSatisfy!(.isSend, staticMap!(toType, fieldNames));
}
}
It is super hacky, but what it basically does is to mark specific types as sendable. If the type has no members it is considered sendable by default. It is also considered sendable if all members are sendable.
struct Foo{}
// sendable because all members are sendable
struct Bar{
int i;
}
// not sendable because we set it explicitly to false
struct Baz{}
enum isSend(T: Baz) = false;
// sendable because Bar is sendable
struct Test1{
Bar bar;
}
//not sendable because Baz is not sendable
struct Test2{
Baz baz;
}
This works fine because everything is in the same module, but it breaks down once I try to implement isSend for another type outside of the module where isSend is defined.
The example is pretty simple and I could possible have used UDA's to implement it.
Another example would be
auto arr = iota(0, 100).array;
This is defined in phobos and creates an array, but it would be a bit awkward if every data structure would have to define its own function. Consider something like this
int[] arr = iota(0, 100).collect!(int[]);
Array!int arr1 = iota(0, 100).collect!(Array!int);
How could this be achieved?
auto collect(Iter, T)(ref Iter iter){...}
auto collect(Iter, A, T: Array!A)(ref Iter iter){...}
I think the problem is that collect accross different modules will be different, is there a way to say that I want to create an overload/specialization for collect in module a for module b?
What other alternatives do I have to extend functionality?
Update:
Basically this is what I want to implement
int[] arr = iota(0, 100).collect!(int[]);
Array!int arr1 = iota(0, 100).collect!(Array!int);
....
Imagine that collect would have been defined in some library. Now I have another container, let us call it List. I want to be able to call collect on a List!T
List!int arr2 = iota(0, 100).collect!(List!int);
But collect doesn't work with List because List is from another library. Now I need to extend the functionality of collect to also work with a List!T.
Can collect be expressed in D?
I tried to implement it with template specialization and I am not sure if that is the right tool.
T collect(T, Range)(auto ref Range range){
static assert(false, "Collect is not implemented for " ~ T.stringof);
}
T collect(A, T: Array!A, Range)(auto ref Range range)
if(isInputRange!Range){
auto arr = Array!A();
foreach(ref e; range){
arr.insert(e);
}
return arr;
}
Now it can be used like this
Array!int arr = iota(0, 100).collect!(int, Array!int)(); //ugly
Rust offers this functionality with collect.
I am trying to make it possible for a programmer (who uses my library) to create nameable instances of type X that are stored inside an instance of class C (or at least are exclusive to that instance).
These are the only two (ugly) solutions I have managed to come up with (needless to say, I am just picking up C++)
1)
class C
{
public:
class XofC
{
public:
XofC() = delete;
XofC(C& mom)
{
mom.Xlist.emplace_front();
ref = Xlist.front();
}
X& access()
{
return ref;
}
private:
X& ref;
};
//etc
private:
std::forward_list<X> Xlist;
friend class XofC;
//etc
}
Problem:
Having to pass everywhere XofC instances.
2)
class C
{
public:
void newX(std::string);
X& getX(std::string);
//etc.
private:
/*possible run-time mapping implementation
std::vector<X> Xvec;
std::unordered_map<std::string, decltype(Xvec.size())> NameMap;
*/
//etc
}
Problem:
This does the job, but since all names of X (std::string) are known at compilation, the overhead of using run-time std::unordered_map<std::string, decltype(Xvec.size())> kind-of bugs me for something this simple.
Possible(?) solution: compile-time replacing of std::string with automatic index (int). Then I could use:
class C
{
public:
void newX(int); //int: unique index calculated at compile time from std::string
X& getX(int); //int: unique index calculated at compile time from std::string
//etc.
private:
std::vector<X> Xvec;
}
Questions:
Is there a 3)?
Is a compile time solution possible for 2)?
This is the real-life situation: I was starting my first C++ "project" and I thought I could use the practice and utility from an awesome user-friendly, simple and fast argument management library. I plan to make an ArgMan class which can parse the argV based on some specified switches. Switches would be named by the programmer descriptively and the trigger strings be specified (e.g. a switch named recurse could have "-r" and "-recursive" as triggers). When necessary, you should be easily able to get the setting of the switch. Implementation detail: ArgMan would have a std::unordered_map<std::string/*a trigger*/, ??/*something linking to the switch to set on*/>. This ensures an almost linear parse of argV relative to argC. How should I approach this?
You could 'abuse' non-type template arguments to get compiletime named instances:
Live on Coliru
Assume we have a data class X:
#include <string>
struct X
{
int has_some_properties;
std::string data;
};
Now, for our named instances, we define some name constants. The trick is, to give them external linkage, so we can use the address as a non-type template argument.
// define some character arrays **with external linkage**
namespace Names
{
extern const char Vanilla[] = "Vanilla";
extern const char Banana [] = "Banana";
extern const char Coconut[] = "Coconut";
extern const char Shoarma[] = "Shoarma";
}
Now, we make a NamedX wrapper that takes a const char* non-type template argument. The wrapper holds a static instance of X (the value).
// now we can "adorn" a `namedX` with the name constants (above)
template <const char* Name>
struct NamedX
{
static X value;
};
template <const char* Name> X NamedX<Name>::value;
Now you can use it like this:
int main()
{
X& vanilla = NamedX<Names::Vanilla>::value;
vanilla = { 42, "Woot!" };
return vanilla.has_some_properties;
}
Note that due to the fact that the template arguments are addresses, no actual string comparison is done. You cannot, e.g. use
X& vanilla = NamedX<"Vanilla">::value;
becuase "Vanilla" is a prvalue without external linkage. So, in fact you could do without some of the complexity and use tag structs instead: Live on Coliru
While Neil's solution did what I asked for, it was too gimmicky to use in my library. Also, sehe's trick is surely useful, but, if I understood correctly, but doesn't seem related to my question. I have decided to emulate the desired behavior using method 1), here is a less broken attempt at it:
class C
{
private:
class X
{
//std::string member;
//etc
};
public:
class XofC
{
public:
XofC(C & _mom) : mom(_mom)
{
mom.Xlist.emplace_front();
tehX = &(Xlist.front());
}
X & get(maybe)
{
if (&maybe != &mom) throw std::/*etc*/;
return &tehX;
}
private:
X * tehX;
C & mom;
};
private:
//etc
std::forward_list<X> Xlist;
friend class XofC;
//etc
};
Usage:
C foo;
bar = C::XofC(foo); //acts like an instance of X, but stored in C, but you have to use:
bar.get(foo)/*reference to the actual X*/.member = "_1_";
Of course, the downside is you have to make sure you pass bar everywhere you need it, but works decently.
This is how it looks like in my tiny argument manager library:
https://raw.github.com/vuplea/arg_manager.h/master/arg_manager.h
I've got a C++ class, with a member function that can take a small-to-large number of parameters. Lets name those parameters, a-f. All parameters have default values. As a part of the python project I am working on, I want to expose this class to python. Currently, the member function looks something like this:
class myClass {
public:
// Constructors - set a-f to default values.
void SetParameters(std::map<std::string, double> &);
private:
double a, b, c, d, e, f;
}
void myClass::SetParameters(std::map<std::string, double> const& params) {
// Code to iterate over the map, and set any found key/value pairs to their
// corresponding variable. i.e.- "a" --> 2.0, would set myClass::a to 2.0
}
Ideally, in Python, I would like to accomplish this using a dict:
>>> A = myModule.myClass();
>>> A.SetParameters({'a': 2.2, 'd': 4.3, b: '9.3'})
In this way, the user could enter the values in any order, and enter any number of them to be over-ridden. Any thoughts on how this could be accomplished in boost::python? It seems to me that I can do this via changing the map input to a boost::python object, and using the extract functions. However, this would require me to change the interface of my library (I'd prefer to keep the std::map interface, and have some intermediary/auto conversion technique for the python version). Thoughts?
I think there's a couple of ways that are easier to accomplish than writing your own converter. You can use boost::python's map_indexing_suite to do the conversion for you, or you can use keyword arguments in python. I personally prefer keyword arguments, as this is the more "Pythonic" way to do this.
So this is your class (I added a typedef for the map):
typedef std::map<std::string, double> MyMap;
class myClass {
public:
// Constructors - set a-f to default values.
void SetParameters(MyMap &);
private:
double a, b, c, d, e, f;
};
Example using map_indexing_suite:
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
using boost::python;
BOOST_PYTHON_MODULE(mymodule)
{
class_<std::map<std::string, double> >("MyMap")
.def(map_indexing_suite<std::map<std::wstring, double> >() );
class_<myClass>("myClass")
.def("SetParameters", &myClass::SetParameters);
}
Example using keyword arguments. This requires using a raw_function wrapper:
using namespace boost::python;
object SetParameters(tuple args, dict kwargs)
{
myClass& self = extract<myClass&>(args[0]);
list keys = kwargs.keys();
MyMap outMap;
for(int i = 0; i < len(keys); ++i) {
object curArg = kwargs[keys[i]];
if(curArg) {
outMap[extract<std::string>(keys[i])] = extract<double>(kwargs[keys[i]]);
}
}
self.SetParameters(outMap);
return object();
}
BOOST_PYTHON_MODULE(mymodule)
{
class_<myClass>("myClass")
.def("SetParameters", raw_function(&SetParameters, 1));
}
this allows you to write stuff like this in Python:
A.SetParameters(a = 2.2, d = 4.3, b = 9.3)
This blog post has a pretty clear description of how to write these converters. The basic pattern is to define a class that has the form:
struct SomeType_from_PyObject
{
SomeType_from_PyObject();
static void* convertible(PyObject* obj_ptr);
static void construct(PyObject* obj_ptr,
converter::rvalue_from_python_stage1_data* data);
};
Where the constructor is responsible for adding this converter to Boost.Python's registry:
SomeType_from_PyObject::SomeType_from_PyObject()
{
converter::registry::push_back(&convertible,
&construct,
type_id<SomeType>());
}
The function convertible tells Boost whether or not this converter can convert the specified Python object:
void* SomeType_from_PyObject::convertible(PyObject* obj_ptr)
{
if (PyMapping_Check(obj_ptr)) {
return obj_ptr;
} else {
return NULL;
}
}
The construct function actually creates an object of the conversion type:
void SomeType_from_PyObject::construct(PyObject* obj_ptr,
converter::rvalue_from_python_stage1_data* data)
{
typedef converter::rvalue_from_python_storage<SomeType> storage_t;
storage_t* the_storage = reinterpret_cast<storage_t*>(data);
void* memory_chunk = the_storage->storage.bytes;
object obj(handle<>(borrowed(obj_ptr)));
SomeType* output = new (memory_chunk) SomeType();
// Use the contents of obj to populate output, e.g. using extract<>
data->convertible = memory_chunk;
}
and then in your inside your BOOST_PYTHON_MODULE, include the line
SomeType_from_PyObject();
I'm just getting my toes wet with boost::python so can't completely answer your question. But the first roadblock I see is guaranteeing that the py dict's keys are all strings. Python dicts can also be keyed on tuples (and I assume more types).
What I would like to do (in C++) is create a 'Parameter' data type which has a value, min, and max. I would then like to create a container for these types.
E.g. I have the following code:
template <typename T>
class ParamT {
public:
ParamT() {
}
ParamT(T _value):value(_value) {
}
ParamT(T _value, T _vmin, T _vmax):value(_value), vmin(_vmin), vmax(_vmax) {
}
void setup(T vmin, T vmax) {
this->vmin = vmin;
this->vmax = vmax;
}
void setup(T value, T vmin, T vmax) {
setup(vmin, vmax);
setValue(value);
}
T operator=(const T & value) {
setValue(value);
}
void setValue(T v) {
value = v;
}
T getValue() {
return value;
}
operator T() {
return getValue();
}
protected:
T value;
T vmin;
T vmax;
};
typedef ParamT<int> Int;
typedef ParamT<float> Float;
typedef ParamT<bool> Bool;
In an ideal world my Api would be something like:
std::map<string, Param> params;
params["speed"] = PFloat(3.0f, 2.1f, 5.0f);
params["id"] = PInt(0, 1, 5);
or
params["speed"].setup(3.0f, 2.1f, 5.0f);
params["id"].setup(0, 1, 5);
and writing to them:
params["speed"] = 4.2f;
params["id"] = 1;
or
params["speed"].setValue(4.2f);
params["id].setValue(1);
and reading:
float speed = params["speed"];
int id = params["id"];
or
float speed = params["speed"].getValue();
int id = params["id"].getValue();
Of course in the code above, ParamT has no base class so I cannot create a map. But even if I create a base class for it which ParamT extends, I obviously cannot have different getValues() which return different types. I thought about many solutions, including setValueI(int i), setValuef(float f), int getValueI(), float getValueF(), or a map for ints, a map for floats etc. But all seem very unclean. Is it possible in C++ to implement the above API?
At the moment I am only concerned with simple types like int, float, bool etc. But I would like to extend this to vectors (my own) and potentially more.
It's a tough concept to implement in C++, as you're seeing. I'm always a proponent of using the Boost library, which has already solved it for you. You can typedef the complex boost variant template class to something more usable in your specific domain, so
typedef boost::variant< int, float, bool > ParamT;
class Param
{
public:
// initialize the variants
Param(ParamT min, ParamT max, ParamT value)
: m_Min(min), m_Max(max), m_Value(value) {}
// example accessor
template<typename OutT>
const ParamT& value()
{
return boost::get<OutT>(m_Value);
}
// other accessors for min, max ...
private:
ParamT m_Min, m_Value, m_Max;
};
Param speed(-10.0f, 10.0f, 0.0f);
float speedValue = speed.value<float>();
Now, to add another type to your variant (eg, long, std::string, whatever) you can just modify the typedef of ParamT; The catch, here, is that the burden of checking the types is on you - it'll throw an exception if you store a float and try to receive an int, but there's no compile-time safety.
If you want to get really crazy, you can implement an overloaded cast operator on a proxy object....
class ProxyValue
{
public:
ProxyValue(ParamT& value) : m_Value(value) {}
template<typename ValueT>
operator ValueT()
{
return boost::get<ValueT>(m_Value);
}
private:
ParamT& m_Value;
};
You'd return this from a non-templated value() function in Param, instead of the variant itself. Now you can assign a value without the template call..
Param speed(-10.0f, 0, 10);
float speedValue = speed.value();
Though fair warning, you're stepping into meta-programming hell here. Here thar be dragons. And as always, this is not a complete solution, just a pointer. YMMV.
Heres a roughly working version showing how to use it, and the failures that are easy to hit.
Ok, I'm bored at work (just waiting for something to compile), so here's another solution. Just have one type Param that stores three Values. Those values can by dynamically typed and can store ints and floats (and anything else you want them to).
class Value
{
private:
union
{
int i,
float f
} val;
DataTypeCode dtc;
public
Value() : val.i(0), dtc(INT) {}
Value(int i) : val.i(i), dtc(INT) {}
Value(float f) : val.f(f), dtc(FLOAT) {}
Value& operator=(int i)
{
val.i=i;
dtc=INT;
return *this;
}
Value& operator=(float f)
{
val.f=f;
dtc=FLOAT;
return *this;
}
operator int()
{
switch (dtc)
{
case INT: return val.i;
case FLOAT: return (int)val.f;
}
return 0;
}
operator float()
{
switch (dtc)
{
case INT: return (float)val.i;
case FLOAT: return val.f;
}
return 0;
}
}
class Param
{
private:
Value value, min, max
public:
Param(Value value, Value min, Value max) : value(value), min(min), max(max) {}
}
note, this still requires that DataTypeCode enum that I have in my other answer.
Now to access it, all you have to do is this:
std::map<string:Param> Params;
Params["speed"]=Param(1.4,0.1,5.6)
float speed=Params["speed"]
the cast operators along with the overloaded constructors and operator= functions will automatically convert among the types for you.
You can use either boost::any (to be able to store any type) or boost::variant (to store any type from a fixed set of prespecified types); however, the boost::program_options library largely already does what you want. I would strongly advise that you use boost::program_options rather than rolling this library yourself. I should point out that there is a major downside to what you are doing; you are validating types manually at runtime, which makes it easy for various errors to slip through. I strongly recommend using protocol buffers as a configuration language, as you get stronger type-checking that way.
A question I have about your design is why do you need to support all these value types? Performance, type safety, numeric accuracy, or simplicity/ease of use? It's going to be tough to get your interface to support all of these.
One simple way to solve the question, as you posed it, would be to pick a single numeric type that supports all the values you are interested in. In general, a double should suffice. It will be obvious to users what is going on under the hood, and you don't need to do anything weird with your implementation.
If you need perfect storage, you could implement your own numeric type that can do conversions (implicit or explicit) to various numeric types, and maintain perfect storage if you convert to/from the same type. If you're really concerned about perfect storage, you could also make it throw if you try to do a conversion back to the wrong type. This is like a strongly typed union. I believe the boost library has a type like this. Edit: Nicholas M T Elliott's answer already mentions this - boost variant.
If you like the even-more-explicit interface that you have here, with your GetValueAsInt/SetValueAsInt interface, you can still make it slightly simpler. Combine the setters, since C++ supports function overloading for parameters: void SetValue(int value) void SetValue(float value). C++ does not support function overloading for return types, though, so you cannot combine the getters.
Edit:
No matter which of these you pick, you're going to have a problem making it generic, or adding new types to it later. You must modify the property map's value type every time you want to support an new class.
The simplest way around this in C++ is to use a void* as your value type, and do casts to convert it to and from your target type. Your library could provide a template wrapper to do this cast, and throw if the cast fails.
This is similar to using "object" in Java/C#
Edit:
As Michael Aaron Safyan suggested, you could use boost::any.
In the end, you need to think about this: must your design include property dictionaries? If it doesn't have to have it, then you could benefit from the compiler's static analysis if you abandon this idea. Any behavior you push off to runtime will cause bugs that you won't find at compile time. It does make it faster to get the code running, but it makes your runtime error handling harder, and can hurt perf.
Well, it's easy to make a container store just about anything. As you said, you could make a common base class and have the map just store a pointer to that. The hard part is knowing what data type they are when you're retrieving them and using it. I have something like this in my main project where I'm mixing compile-time type determined c++ code and run-time type determined code from another language. So I embed into the class it's datatype code so that I can do a switch() statement on it. You could have something like this:
enum DataTypeCode
{
UNKNOWN,
INT,
FLOAT
};
template <class DataType>
DataTypeCode GetDataTypeCode()
{
return UNKNOWN;
}
template <>
DataTypeCode GetDataTypeCode<int>()
{
return INT;
}
template <>
DataTypeCode GetDataTypeCodE<float>(
{
return FLOAT;
}
class BaseParam
{
public:
virtual ~BaseParam() {}
virtual DataTypeCode GetDataTypeCode()=0;
};
template <class DataType>
class Param : public BaseParam
{
public:
DataTypeCode GetDataTypeCode()
{
return ::GetDataTypeCode<DataType>();
}
}
and you have to store it as a pointer to take care of polymorphism:
std::map<string,BaseParam*> Params
Params["speed"]=new Param<float>(...)
BaseParam* pMyParam=Params["speed"];
switch (pMyParam->GetDataTypeCode())
{
case INT:
//dosomething with int types
case FLOAT:
//dosomething with float types
}
It's not pretty, but it'll get the job done. Normally, I'll end up wrapping the std::map<string, BaseParam*> inside of another class to hide the fact that it's storing a pointers. I like to make my APIs hide the use of pointers as much as possible, it makes it easier for the junior programmers on my team to deal with it.