Introduction
A union list ( ul
) is a polymorphic type that can hold one value from one of the predetermined set of types. The precise type that a union list represents is determined at run-time when constructed based on the type of the value it is initialized with.
Motivation
The primary role of a union list is to allow polymorphism between completely unrelated types. Unions have too many unreasonable limitations to be useful in most situations.
Using a Union List
A union list is declared recursively as follows:
ul<int, ul<char const*, ul_end> >
Note that the last element has to be of type ul_end
. Usually we would use a union list with a typedef
to save typing:
typedef ul<int, ul<char const*, ul_end> > IntOrString_T;
Declaring a variable of type ul
requires an initializing value, such as:
IntOrString_T i(42);
IntOrString_T s("hello");
Once a ul
is constructed, it provides the index of the type that it represents.
cout << i.TypeIndex() << endl;
cout << s.TypeIndex() << endl;
Of course, what is most important is accessing the data. This is done by passing the index of the type as a template parameter:
cout << i.Get<0>() << endl;
cout << s.Get<1>() << endl;
Of course, it can be a bit tricky to always correctly match the index passed to Get()
with the correct internal data index, but don't worry, the union list will throw an exception if you make a mistake:
cout << i.Get<1>() << endl;
The Code
Here is the entire source code for the union list:
struct ul_end {
const TypeIndex() { return 0; }
};
template<typename Head_T, typename Tail_T, int N>
struct TypeList {
typedef typename TypeList<
typename Tail_T::H_T,
typename Tail_T::T_T,
N - 1>::type
type;
};
template<typename Head_T, typename Tail_T>
struct TypeList<Head_T, Tail_T, 0> {
typedef typename Head_T type;
};
template<typename Head_T, typename Tail_T>
struct ul
{
typedef typename ul<Head_T, Tail_T> self;
template<typename T>
ul(T x) : tail(x), tag(false) { }
template<>
ul(Head_T x) : head(x), tag(true) { }
ul(const self& x) : head(x.head), tail(x.tail), tag(x.tag) { }
ul() { };
typedef typename Head_T H_T;
typedef typename Tail_T T_T;
Head_T head;
Tail_T tail;
bool tag;
template<int N>
typename TypeList<Head_T, Tail_T, N>::type& Get() {
if (N != TypeIndex()) {
throw 0;
}
return *(TypeList<Head_T, Tail_T, N>::type*)InternalGet<N>();
};
template<int N>
void* InternalGet() {
return tail.InternalGet<N-1>();
};
template<>
void* InternalGet<0>() {
return &head;
};
const int TypeIndex() {
return tag ? 0 : tail.TypeIndex() + 1;
}
};
Here is some example code to get you going:
#include <string>
#include <iostream>
using namespace std;
typedef ul<
int,
ul<
char,
ul<
bool,
ul<
double,
ul<
string,
ul_end
>
>
>
>
> test_type;
void output(test_type x) {
switch(x.TypeIndex()) {
case 0 : cout << "int : " << x.Get<0>() << endl; break;
case 1 : cout << "char : " << x.Get<1>() << endl; break;
case 2 : cout << "bool : " << x.Get<2>() << endl; break;
case 3 : cout << "float : " << x.Get<3>() << endl; break;
case 4 : cout << "string : " << x.Get<4>() << endl; break;
}
}
int main() {
output('a');
output(3.141);
output(42);
output(string("Hello world"));
output(true);
getchar();
return 0;
}
Postscript
It would be preferable to have the TypeList
type as a member template, as it would save a lot of scaffolding code, but unfortunately that is not portable across platforms. Member type template specializations are not part of the standard for some obscure reason.