Union Lists: Single Value, Multiple Types






4.63/5 (15 votes)
Jan 4, 2005
1 min read

69037
A union list is a simple union style type which can hold one value of any of a fixed number of types.
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; // outputs 0 cout << s.TypeIndex() << endl; // outputs 1
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; // outputs 42 cout << s.Get<1>() << endl; // outputs "hello"
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; // throws an exception
The Code
Here is the entire source code for the union list:
// public domain by Christopher Diggins, January 2005 // // This is a utility class which allows the user to define a // union of an arbitrary number of a list of types
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.