65.9K
CodeProject is changing. Read more.
Home

Union Lists: Single Value, Multiple Types

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (15 votes)

Jan 4, 2005

1 min read

viewsIcon

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.