Reputation: 369
I need to package a fixed number of values of arbitrary types in a class. Then I need to be able to pass each parameter through a switch according the their type. The types of the parameters are basic C types and pointers to stuff (a specific and limited number of "stuff"), so nothing complicated.
That "Parameter" class needs to be light (in space as well as in processing).
This is an example of how I need to use it:
void MyFunc( const Parameters &Params )
{
// for loop
switch( Params(0).GetType() ) {
case MY_INT_TYPE: int ValInt = Params(0).Get<int>(); ...
case MY_PTR_TO_MY_STUFF1: MyStuff1 *ValS1 = Params(0).Get<MyStuff1*>(); ...
...
}
}
Parameters MyParams(2);
MyParams.Set<int>(0, 123);
MyParams.Set<MyStuff1*>(1, &SomeClassInstance);
MyFunc( MyParams );
...
MyParams.Set<float>(0, 123.456); // The same variable in the same scope
MyParams.Set<int*>(1, &Val);
MyFunc( MyParams );
Of course I can specialize for all types manually and store in a union, that is the brute force approach. I keep thinking there is an easier way to do this, but can't figure it out. I can use type traits to store the type info, but I am stuck for the value. And casting the value is not an option.
Any pointer (figurative pointers that is)?
Upvotes: 1
Views: 318
Reputation: 2999
An ideal implementation would use a variant type (e.g. boost::variant
) parameterised by the valid set of parameter types. Here's an example of how to use a variant type to solve the problem:
typedef Variant<int, MyStuff1*> Parameter;
int main()
{
MyStuff1 SomeClassInstance;
Parameter MyParams[2] = { 123, &SomeClassInstance };
int ValInt = MyParams[0].Get<int>();
MyStuff1* ValS1 = MyParams[1].Get<MyStuff1*>();
}
And here's an example of how to implement a variant type, if you can't use boost
.
#include <type_traits>
template<typename T, typename U>
struct MaxSize
{
static const size_t value = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U);
};
struct ValueBase
{
virtual ~ValueBase()
{
}
};
template<typename T>
struct ValueGeneric : ValueBase
{
T value;
ValueGeneric(const T& value) : value(value)
{
}
};
template<typename T1, typename T2>
class Variant
{
typename std::aligned_union<MaxSize<T1, T2>::value, T1, T2>::type storage;
Variant(const Variant&); // not copyable
Variant operator=(const Variant&); // not assignable
public:
template<typename T>
Variant(const T& value)
{
new(&storage) ValueGeneric<T>(value);
}
~Variant()
{
static_cast<ValueBase*>(static_cast<void*>(&storage))->~ValueBase();
}
void Set(const T1& value)
{
SetImpl(value);
}
void Set(const T2& value)
{
SetImpl(value);
}
void Get(const T1& value)
{
this->~Variant();
new (this) Variant(value);
}
template<typename T>
bool IsA() const
{
return typeid(T) == typeid(*static_cast<const ValueBase*>(static_cast<const void*>(&storage)));
}
template<typename T>
T& Get()
{
assert(IsA<T>());
return *static_cast<T*>(static_cast<void*>(&storage));
}
template<typename T>
const T& Get() const
{
assert(IsA<T>());
return *static_cast<const T*>(static_cast<const void*>(&storage));
}
private:
template<typename T>
void SetImpl(const T& value)
{
this->~Variant();
new (this) Variant(value);
}
};
This implementation is intended as an example and only supports two types - it would be relatively easy to extend to support more. You would want to use template-parameter-packs and move-constructors to achieve the cleanest and most efficient implementation.
Upvotes: 0
Reputation: 4677
Regarding last your comment I give you next answer :)
class ParamsProcessor
{
// custom parameter reciever for type T1
template<>
public ParamsProcessor& in<T1>(const T1& o)
{ ... }
// custom parameter reciever for type T2
template<>
public ParamsProcessor& in<T2>(T2 t2)
{ ... }
// default parameter reciever
template<class T>
public ParamsProcessor& operator in(const T& o)
{
// some generic way if the project allows
...
return *this
}
void Process()
{ ... }
};
ParamsProcessor pp;
pp.in<T1>(0,123)
.in<T2>(0,123.456)
.in("asds");
.Process();
Sure better if each overload will process itself. I am really hope that my answers will cover your needs.
Upvotes: 1
Reputation: 4677
Another way that allows to get rid of switch is use enum and array of functors:
enum TheTypes { T1, T2, T3, TheTypesCount};
boost::function<void (const Parameters &Params)> Processors[] = {
boost::bind(&T1::ProcessingMethod, t1Obj, _1),
boost::bind(&T2::ProcessingStaticMethod, _1),
boost::bind(RawFunction, _1)
};
And that allows you substitute intrusive stitch with simple construct:
void MyFunc( const Parameters &Params )
{
// for loop
Processors[Params(0).GetType()](Params(0));
}
Congratulaions!
If you are maniac than will discuss next approach. What about creating static decorator (mixin) for automaticially do following:
Upvotes: 0
Reputation: 4677
Anyway there will be at once one switch if you will not use run-time polymorphism. If you will use static polymorphism than you need either specialization or switch.
Its good approach to use type2enum and enum2type mapping:
type2enum:
// preparition
template<class T> type2enum();
#define TYPE2ENUM_SPEC(TYPE, ENUM) \
template<> type2enum<TYPE>() \
{ return ENUM; }
enum { T1enum, T2enum, T3enum }
TYPE2ENUM_SPEC(type1_t, T1enum);
TYPE2ENUM_SPEC(type2_t, T2enum);
TYPE2ENUM_SPEC(some_third_type_t, T3enum);
and backward enum2type:
// preparition
template<int Enum>
struct enum2type;
#define ENUM2TYPE_SPEC(ENUM, TYPE) \
template<> struct Enum2Type<ENUM> \
{ typedef TYPE type_t; }
// and generic macro
#define CREATE_TYPE_MAPPING(TYPE, INTEGER) \
#define TYPE2ENUM_SPEC(TYPE, INTEGER) \
#define ENUM2TYPE_SPEC(INTEGER, TYPE)
and the sample of code:
we have Type1, Type2, Type3 and enum:
enum {T1, T2, T3};
CREATE_TYPE_MAPPING(Type1, T1);
CREATE_TYPE_MAPPING(Type2, T2);
CREATE_TYPE_MAPPING(Type3, T3);
than use it for mapping type to enum:
int typeId = type2enum<Type1>();
or constant enum to type:
typedef Enum2Type<ConstEnumType>::type_t some_t;
and for runtime enum to type you use something like:
template<class ObjT>
void DoWithTypeIdValue(int enumValue, const ObjT& obj)
{
switch(enumValue)
{
case enumType1:
obj.do<Enum2Type<enumType1>::type_t>();
break;
case enumType2:
obj.do<Enum2Type<enumType2>::type_t>();
break;
case enumType3:
obj.do<Enum2Type<enumType3>::type_t>();
break;
default:
assert(!"Unknown type constant");
}
}
Upvotes: 0