# dlib C++ Library - type_safe_union_kernel.h

    // Copyright (C) 2009  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_TYPE_SAFE_UNIOn_h_
    #define DLIB_TYPE_SAFE_UNIOn_h_

    #include "type_safe_union_kernel_abstract.h"
    #include <new>
    #include <iostream>
    #include <type_traits>
    #include <functional>
    #include "../serialize.h"
    #include "../invoke.h"

    namespace dlib
    {
        // ---------------------------------------------------------------------

        class bad_type_safe_union_cast : public std::bad_cast
        {
        public:
              virtual const char * what() const throw()
              {
                  return "bad_type_safe_union_cast";
              }
        };

        // ---------------------------------------------------------------------

        template<typename T>
        struct in_place_tag { using type = T;};

        // ---------------------------------------------------------------------

        template <typename... Types> class type_safe_union;

        template<typename Tsu>
        struct type_safe_union_size;

        template<typename... Types>
        struct type_safe_union_size<type_safe_union<Types...>> : std::integral_constant<size_t, sizeof...(Types)> {};

        template<typename Tsu> struct type_safe_union_size<const Tsu>           : type_safe_union_size<Tsu> {};
        template<typename Tsu> struct type_safe_union_size<volatile Tsu>        : type_safe_union_size<Tsu> {};
        template<typename Tsu> struct type_safe_union_size<const volatile Tsu>  : type_safe_union_size<Tsu> {};

        // ---------------------------------------------------------------------

        namespace detail
        {
            template<size_t I, typename... Ts>
            struct nth_type;

            template<size_t I, typename T0, typename... Ts>
            struct nth_type<I, T0, Ts...> : nth_type<I-1, Ts...> {};

            template<typename T0, typename... Ts>
            struct nth_type<0, T0, Ts...> { using type = T0; };
        }

        template <size_t I, typename TSU>
        struct type_safe_union_alternative;

        template <size_t I, typename... Types>
        struct type_safe_union_alternative<I, type_safe_union<Types...>> : detail::nth_type<I, Types...>{};

        template<size_t I, typename TSU>
        using type_safe_union_alternative_t = typename type_safe_union_alternative<I, TSU>::type;

        template <size_t I, typename TSU>
        struct type_safe_union_alternative<I, const TSU>
        { using type = typename std::add_const<type_safe_union_alternative_t<I, TSU>>::type; };

        template <size_t I, typename TSU>
        struct type_safe_union_alternative<I, volatile TSU>
        { using type = typename std::add_volatile<type_safe_union_alternative_t<I, TSU>>::type; };

        template <size_t I, typename TSU>
        struct type_safe_union_alternative<I, const volatile TSU>
        { using type = typename std::add_cv<type_safe_union_alternative_t<I, TSU>>::type; };

        // ---------------------------------------------------------------------

        namespace detail
        {
            // ---------------------------------------------------------------------

            template <typename T, typename First, typename... Rest>
            struct is_any : std::integral_constant<bool, is_any<T,First>::value || is_any<T,Rest...>::value> {};

            template <typename T, typename First>
            struct is_any<T,First> : std::is_same<T,First> {};

            // ---------------------------------------------------------------------

            template <int nTs, typename T, typename... Ts>
            struct type_safe_union_type_id_impl
                    : std::integral_constant<int, -1 - nTs> {};

            template <int nTs, typename T, typename T0, typename... Ts>
            struct type_safe_union_type_id_impl<nTs, T, T0, Ts...>
                    : std::integral_constant<int, std::is_same<T,T0>::value ? 1 : type_safe_union_type_id_impl<nTs, T,Ts...>::value + 1> {};

            template <typename T, typename... Ts>
            struct type_safe_union_type_id : type_safe_union_type_id_impl<sizeof...(Ts),T,Ts...>{};

            template <typename T, typename... Ts>
            struct type_safe_union_type_id<in_place_tag<T>, Ts...> : type_safe_union_type_id<T,Ts...>{};

            // ---------------------------------------------------------------------
        }

        template <typename... Types>
        class type_safe_union
        {
            /*!
                CONVENTION
                    - is_empty() ==  (type_identity == 0)
                    - contains<T>() == (type_identity == get_type_id<T>())
                    - mem == the aligned block of memory on the stack which is
                      where objects in the union are stored
            !*/
        public:
            template <typename T>
            static constexpr int get_type_id ()
            {
                return detail::type_safe_union_type_id<T,Types...>::value;
            }

        private:
            template<typename T>
            struct is_valid : detail::is_any<T,Types...> {};

            template<typename T>
            using is_valid_check = typename std::enable_if<is_valid<T>::value, bool>::type;

            template <size_t I>
            using get_type_t = type_safe_union_alternative_t<I, type_safe_union>;

            typename std::aligned_union<0, Types...>::type mem;
            int type_identity = 0;

            template<
                typename F,
                typename TSU,
                std::size_t I
            >
            static void apply_to_contents_as_type(
                F&& f,
                TSU&& me
            )
            {
                std::forward<F>(f)(me.template unchecked_get<get_type_t<I>>());
            }

            template<
                typename F,
                typename TSU,
                std::size_t... I
            >
            static void apply_to_contents_impl(
                F&& f,
                TSU&& me,
                dlib::index_sequence<I...>
            )
            {
                using func_t = void(*)(F&&, TSU&&);

                const func_t vtable[] = {
                    /*! Empty (type_identity == 0) case !*/
                    [](F&&, TSU&&) {
                    },
                    /*! Non-empty cases !*/
                    &apply_to_contents_as_type<F&&,TSU&&,I>...
                };

                return vtable[me.get_current_type_id()](std::forward<F>(f), std::forward<TSU>(me));
            }

            template <typename T>
            const T& unchecked_get() const
            {
                return *reinterpret_cast<const T*>(&mem);
            }

            template <typename T>
            T& unchecked_get()
            {
                return *reinterpret_cast<T*>(&mem);
            }

            struct destruct_helper
            {
                template <typename T>
                void operator() (T& item) const
                {
                    item.~T();
                }
            };

            void destruct ()
            {
                apply_to_contents(destruct_helper{});
                type_identity = 0;
            }

            template <typename T, typename... Args>
            void construct (
                Args&&... args
            )
            {
                destruct();
                new(&mem) T(std::forward<Args>(args)...);
                type_identity = get_type_id<T>();
            }

            struct assign_to
            {
                /*!
                    This class assigns an object to `me` using std::forward.
                !*/
                assign_to(type_safe_union& me) : _me(me) {}

                template<typename T>
                void operator()(T&& x)
                {
                    using U = typename std::decay<T>::type;

                    if (_me.type_identity != get_type_id<U>())
                    {
                        _me.construct<U>(std::forward<T>(x));
                    }
                    else
                    {
                        _me.template unchecked_get<U>() = std::forward<T>(x);
                    }
                }

                type_safe_union& _me;
            };

            struct move_to
            {
                /*!
                    This class move assigns an object to `me`.
                !*/
                move_to(type_safe_union& me) : _me(me) {}

                template<typename T>
                void operator()(T& x)
                {
                    if (_me.type_identity != get_type_id<T>())
                    {
                        _me.construct<T>(std::move(x));
                    }
                    else
                    {
                        _me.template unchecked_get<T>() = std::move(x);
                    }
                }

                type_safe_union& _me;
            };

            struct swap_to
            {
                /*!
                    This class swaps an object with `me`.
                !*/
                swap_to(type_safe_union& me) : _me(me) {}
                template<typename T>
                void operator()(T& x)
                /*!
                    requires
                        - _me.contains<T>() == true
                !*/
                {
                    using std::swap;
                    swap(_me.unchecked_get<T>(), x);
                }

                type_safe_union& _me;
            };

        public:

            type_safe_union() = default;

            type_safe_union (
                const type_safe_union& item
            ) : type_safe_union()
            {
                item.apply_to_contents(assign_to{*this});
            }

            type_safe_union& operator=(
                const type_safe_union& item
            )
            {
                if (item.is_empty())
                    destruct();
                else
                    item.apply_to_contents(assign_to{*this});
                return *this;
            }

            type_safe_union (
                type_safe_union&& item
            ) : type_safe_union()
            {
                item.apply_to_contents(move_to{*this});
                item.destruct();
            }

            type_safe_union& operator= (
                type_safe_union&& item
            )
            {
                if (item.is_empty())
                {
                    destruct();
                }
                else
                {
                    item.apply_to_contents(move_to{*this});
                    item.destruct();
                }
                return *this;
            }

            template <
                typename T,
                is_valid_check<typename std::decay<T>::type> = true
            >
            type_safe_union (
                T&& item
            ) : type_safe_union()
            {
                assign_to{*this}(std::forward<T>(item));
            }

            template <
                typename T,
                is_valid_check<typename std::decay<T>::type> = true
            >
            type_safe_union& operator= (
                T&& item
            )
            {
                assign_to{*this}(std::forward<T>(item));
                return *this;
            }

            template <
                typename T,
                typename... Args,
                is_valid_check<T> = true
            >
            type_safe_union (
                in_place_tag<T>,
                Args&&... args
            )
            {
                construct<T>(std::forward<Args>(args)...);
            }

            ~type_safe_union()
            {
                destruct();
            }

            void clear()
            {
                destruct();
            }

            template <
                typename T,
                typename... Args,
                is_valid_check<T> = true
            >
            void emplace(
                Args&&... args
            )
            {
                construct<T>(std::forward<Args>(args)...);
            }

            template <typename F>
            void apply_to_contents(
                F&& f
            )
            {
                apply_to_contents_impl(std::forward<F>(f), *this, dlib::make_index_sequence<sizeof...(Types)>{});
            }

            template <typename F>
            void apply_to_contents(
                F&& f
            ) const
            {
                apply_to_contents_impl(std::forward<F>(f), *this, dlib::make_index_sequence<sizeof...(Types)>{});
            }

            template <typename T>
            bool contains (
            ) const
            {
                return type_identity == get_type_id<T>();
            }

            bool is_empty (
            ) const
            {
                return type_identity == 0;
            }

            int get_current_type_id() const
            {
                return type_identity;
            }

            void swap (
                type_safe_union& item
            )
            {
                if (type_identity == item.type_identity)
                {
                    item.apply_to_contents(swap_to{*this});
                }
                else if (is_empty())
                {
                    item.apply_to_contents(move_to{*this});
                    item.destruct();
                }
                else if (item.is_empty())
                {
                    apply_to_contents(move_to{item});
                    destruct();
                }
                else
                {
                    type_safe_union tmp;
                    swap(tmp);      // this -> tmp
                    swap(item);     // item -> this
                    tmp.swap(item); // tmp (this) -> item
                }
            }

            template <
                typename T,
                is_valid_check<T> = true
            >
            T& get(
            )
            {
                if (type_identity != get_type_id<T>())
                    construct<T>();
                return unchecked_get<T>();
            }

            template <
                typename T
            >
            T& get(
                in_place_tag<T>
            )
            {
                return get<T>();
            }

            template <
                typename T,
                is_valid_check<T> = true
            >
            const T& cast_to (
            ) const
            {
                if (contains<T>())
                    return unchecked_get<T>();
                else
                    throw bad_type_safe_union_cast();
            }

            template <
                typename T,
                is_valid_check<T> = true
            >
            T& cast_to (
            )
            {
                if (contains<T>())
                    return unchecked_get<T>();
                else
                    throw bad_type_safe_union_cast();
            }
        };

        template <typename ...Types>
        inline void swap (
            type_safe_union<Types...>& a,
            type_safe_union<Types...>& b
        ) { a.swap(b); }

        namespace detail
        {
            template<
                typename F,
                typename TSU,
                std::size_t... I
            >
            void for_each_type_impl(
                F&& f,
                TSU&& tsu,
                dlib::index_sequence<I...>
            )
            {
                using Tsu = typename std::decay<TSU>::type;
                (void)std::initializer_list<int>{
                    (std::forward<F>(f)(
                            in_place_tag<type_safe_union_alternative_t<I, Tsu>>{},
                            std::forward<TSU>(tsu)),
                     0
                    )...
                };
            }

            template<
                typename R,
                typename F,
                typename TSU,
                std::size_t I
            >
            R visit_impl_as_type(
                F&& f,
                TSU&& tsu
            )
            {
                using Tsu = typename std::decay<TSU>::type;
                using T   = type_safe_union_alternative_t<I, Tsu>;
                return dlib::invoke(std::forward<F>(f), tsu.template cast_to<T>());
            }

            template<
                typename R,
                typename F,
                typename TSU,
                std::size_t... I
            >
            R visit_impl(
                F&& f,
                TSU&& tsu,
                dlib::index_sequence<I...>
            )
            {
                using func_t = R(*)(F&&, TSU&&);

                const func_t vtable[] = {
                    /*! Empty (type_identity == 0) case !*/
                    [](F&&, TSU&&) {
                        return R();
                    },
                    /*! Non-empty cases !*/
                    &visit_impl_as_type<R,F&&,TSU&&,I>...
                };

                return vtable[tsu.get_current_type_id()](std::forward<F>(f), std::forward<TSU>(tsu));
            }
        }

        template<
            typename TSU,
            typename F
        >
        void for_each_type(
            F&& f,
            TSU&& tsu
        )
        {
            using Tsu = typename std::decay<TSU>::type;
            static constexpr std::size_t Size = type_safe_union_size<Tsu>::value;
            detail::for_each_type_impl(std::forward<F>(f), std::forward<TSU>(tsu), dlib::make_index_sequence<Size>{});
        }

        template<
            typename F,
            typename TSU,
            typename Tsu = typename std::decay<TSU>::type,
            typename T0  = type_safe_union_alternative_t<0, Tsu>
        >
        auto visit(
            F&& f,
            TSU&& tsu
        ) -> dlib::invoke_result_t<F, decltype(tsu.template cast_to<T0>())>
        {
            using ReturnType = dlib::invoke_result_t<F, decltype(tsu.template cast_to<T0>())>;
            static constexpr std::size_t Size = type_safe_union_size<Tsu>::value;
            return detail::visit_impl<ReturnType>(std::forward<F>(f), std::forward<TSU>(tsu), dlib::make_index_sequence<Size>{});
        }

        namespace detail
        {
            struct serialize_helper
            {
                serialize_helper(std::ostream& out_) : out(out_) {}

                template <typename T>
                void operator() (const T& item) const
                {
                    serialize(item, out);
                }

                std::ostream& out;
            };

            struct deserialize_helper
            {
                deserialize_helper(
                    std::istream& in_,
                    int index_
                ) : index(index_),
                    in(in_)
                {}

                template<typename T, typename TSU>
                void operator()(in_place_tag<T>, TSU&& x)
                {
                    if (index == x.template get_type_id<T>())
                        deserialize(x.template get<T>(), in);
                }

                const int index = -1;
                std::istream& in;
            };
        } // namespace detail

        template<typename... Types>
        inline void serialize (
            const type_safe_union<Types...>& item,
            std::ostream& out
        )
        {
            try
            {
                serialize(item.get_current_type_id(), out);
                item.apply_to_contents(detail::serialize_helper(out));
            }
            catch (serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while serializing an object of type type_safe_union");
            }
        }

        template<typename... Types>
        inline void deserialize (
            type_safe_union<Types...>& item,
            std::istream& in
        )
        {
            try
            {
                int index = -1;
                deserialize(index, in);

                if (index == 0)
                    item.clear();
                else if (index > 0 && index <= (int)sizeof...(Types))
                    for_each_type(detail::deserialize_helper(in, index), item);
                else
                    throw serialization_error("bad index value. Should be in range [0,sizeof...(Types))");
            }
            catch(serialization_error& e)
            {
                throw serialization_error(e.info + "\n   while deserializing an object of type type_safe_union");
            }
        }

    #if __cplusplus >= 201703L

        template<typename ...Base>
        struct overloaded_helper : Base...
        {
            template<typename... T>
            overloaded_helper(T&& ... t) : Base{std::forward<T>(t)}... {}

            using Base::operator()...;
        };

    #else

        template<typename Base, typename ... BaseRest>
        struct overloaded_helper: Base, overloaded_helper<BaseRest...>
        {
            template<typename T, typename ... TRest>
            overloaded_helper(T&& t, TRest&& ...trest) :
                Base{std::forward<T>(t)},
                overloaded_helper<BaseRest...>{std::forward<TRest>(trest)...}
            {}

            using Base::operator();
            using overloaded_helper<BaseRest...>::operator();
        };

        template<typename Base>
        struct overloaded_helper<Base> : Base
        {
            template<typename T>
            overloaded_helper<Base>(T&& t) : Base{std::forward<T>(t)}
            {}

            using Base::operator();
        };

    #endif //__cplusplus >= 201703L

        template<typename... T>
        overloaded_helper<typename std::decay<T>::type...> overloaded(T&&... t)
        {
            return overloaded_helper<typename std::decay<T>::type...>{std::forward<T>(t)...};
        }
    }

    #endif // DLIB_TYPE_SAFE_UNIOn_h_