// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2012 Alexander Warg <warg@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * This file is part of TUD:OS and distributed under the terms of the
 * GNU General Public License 2.
 * Please see the COPYING-GPL-2 file for details.
 *
 * As a special exception, you may use this file as part of a free software
 * library without restriction.  Specifically, if other files instantiate
 * templates or use macros or inline functions from this file, or you compile
 * this file and link it with other files to produce an executable, this
 * file does not by itself cause the resulting executable to be covered by
 * the GNU General Public License.  This exception does not however
 * invalidate any other reasons why the executable file might be covered by
 * the GNU General Public License.
 */

#pragma once

#include "type_traits"
#include <new>

namespace cxx {

template< typename BASE, typename ...IMPLS >
class Union
{
  Union(Union const &) = delete;
  Union &operator = (Union const &) = delete;

public:
  Union() { construct<BASE>(); }

  BASE *get()
  { return reinterpret_cast<BASE*>(_d); }

  BASE const *get() const
  { return reinterpret_cast<BASE const*>(_d); }

  BASE *operator -> () { return get(); }
  BASE &operator * () { return *get(); }
  BASE const *operator -> () const { return get(); }
  BASE const &operator * () const { return *get(); }

  template< typename T >
  T *construct()
  {
    BASE *test = reinterpret_cast<T*>(10); static_cast<void>(test);
    static_assert(sizeof(T) <= sizeof(_d), "dynamic type of union too big");
    return new ((void*)_d) T;
  }

  template< typename T, typename ...ARGS >
  T *construct(ARGS ...args)
  {
    BASE *test = reinterpret_cast<T*>(10); static_cast<void>(test);
    static_assert(sizeof(T) <= sizeof(_d), "dynamic type of union too big");
    return new ((void*)_d) T(cxx::forward<ARGS>(args)...);
  }

  void destroy()
  { get()->~BASE(); construct<BASE>(); }

private:
  template< unsigned ...X >
  struct Max;

  template< unsigned Z >
  struct Max<Z> { enum { value = Z }; };

  template< unsigned X, unsigned Y >
  struct Max<X,Y> { enum { value = X > Y ? X : Y }; };

  template< unsigned X, unsigned ...Y >
  struct Max<X, Y...> { enum { value = Max<X, Max<Y...>::value>::value }; };

  enum
  {
    Size = Max<sizeof(BASE), sizeof(IMPLS)...>::value,
    Words = (Size + sizeof(unsigned long) - 1) / sizeof(unsigned long)
  };

  unsigned long _d[Words];
};

}
