Let's assume, that we have an enumered type:
enum DataType { INT, DOUBLE };
And a type mapper:
template<DataType T>
struct TypeTraits {};
template<>
struct TypeTraits<INT> { typedef int T; };
template<>
struct TypeTraits<DOUBLE> { typedef double T; };
And a few templates which represents operations (don't bother about ugly void pointers, and C-like typecasts):
struct Operation {
DataType rettype;
Operation(DataType rettype) : rettype(rettype);
virtual void* compute();
};
template<DataType RetType>
class Constant : public Operation {
typedef typename TypeTraits<RetType>::T RType;
RType val;
Constant(RType val) : val(val), Operation(RetType) {};
virtual void* compute(){ return &val; }
};
template<DataType T1, DataType T2, DataType RetType>
class Add : public Operation {
typedef typename TypeTraits<RetType>::T1 T1Type;
typedef typename TypeTraits<RetType>::T2 T2Type;
typedef typename TypeTraits<RetType>::RetType RType;
RType val;
Operation *c1, *c2;
Add(Operation *c1, Operation *c2) : c1(c1), c2(c2), Operation(RetType) {};
virtual void* compute(){
T1Type *a = (T1Type *)c1->compute();
T2Type *b = (T2Type *)c2->compute();
val = *a + *b;
return &val;
}
};
And a abstract tree representation:
class AbstractNode {
enum Type { ADD, INT_CONSTANT, DOUBLE_CONSTANT };
Type type;
int intval;
double doubleval;
child1 *AbstractNode;
child2 *AbstractNode;
}
We're reading a serialized abstract tree from input in order to translate it into an operation tree, and then - compute a result.
We want to write something like:
algebrator(Operation *op){
if(op->type == AbstractNode::INT_CONSTANT)
return new Constant<INT>(op->intval);
else if(op->type == AbstractNode::DOUBLE_CONSTANT)
return new Constant<DOUBLE>(op->doubleval);
else {
Operation *c1 = algebrator(op->child1),
*c2 = algebrator(op->child2);
DataType rettype = add_types_resolver(c1->rettype, c2->rettype);
return new Add<c1->rettype, c2->rettype, rettype>(c1, c2);
}
}
where add_types_resolver is something which specifies the return type of add operation based on operation arguments types.
And we fail of course and compiler will hit us into the faces. We can't use a variable as a template variable! It's because all information needed to instantiate template must be available during the compliation!
And now - the question.
Is there any other solution than writing a plenty of if-else, or switch-case statements? Can't we ask a compiler in any way to expand all cases during compilation? Template is parametrized by the enum, so we have a guarantee, that such process is finite.
And please - don't write responses like "I think the whole example is messed up". I just want to know if there's a way to feed the template with variable, knowing it's from a finite, small set.
The whole thing may seem like an overkill, but I'm really curious how can I instantiate classes in such unusual situations.
boost::mplto have the compiler generate all if/switch cases for you. It's not straight forward though.