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.
Quick’n’dirty solution with macros:
column_type.cc:
typed_call_test.cc (usage example):
typed_call.cc (“library”):
To take this solution “level up” you can replace macro with a function (still containing similiar switch construct). But probably you would like to pass a functor (object with () operator) as a parameter of this function instead of an ordinary function like in this macro. Btw: this is exactly how they do it in Google.
1st sidenote: hi to my classmate from the Columnar and Distributed DataWarehouses course on University of Warsaw! This course is generating a lot of mind-bending C++ template questions 🙂
2nd sidenote: here is how my equivalent of Typetraits looks like: