// Range v3 library
//
//  Copyright Eric Niebler 2014-present
//
//  Use, modification and distribution is subject to the
//  Boost Software License, Version 1.0. (See accompanying
//  file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
// Project home: https://github.com/ericniebler/range-v3
//

#ifndef RANGES_V3_DETAIL_VARIANT_HPP
#define RANGES_V3_DETAIL_VARIANT_HPP

#include <iterator>
#include <memory>
#include <new>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>

#include <meta/meta.hpp>

#include <concepts/concepts.hpp>

#include <range/v3/range_fwd.hpp>

#include <range/v3/functional/compose.hpp>
#include <range/v3/functional/identity.hpp>
#include <range/v3/functional/invoke.hpp>
#include <range/v3/iterator/concepts.hpp>
#include <range/v3/iterator/traits.hpp>
#include <range/v3/utility/get.hpp>

#include <range/v3/detail/disable_warnings.hpp>

namespace ranges
{
    template<std::size_t I>
    struct emplaced_index_t;

    template<std::size_t I>
    struct emplaced_index_t : meta::size_t<I>
    {};

#if RANGES_CXX_INLINE_VARIABLES < RANGES_CXX_INLINE_VARIABLES_17
    namespace
    {
        template<std::size_t I>
        constexpr auto & emplaced_index = static_const<emplaced_index_t<I>>::value;
    }
#else  // RANGES_CXX_INLINE_VARIABLES >= RANGES_CXX_INLINE_VARIABLES_17
    template<std::size_t I>
    inline constexpr emplaced_index_t<I> emplaced_index{};
#endif // RANGES_CXX_INLINE_VARIABLES

    struct bad_variant_access : std::logic_error
    {
        explicit bad_variant_access(std::string const & what_arg)
          : std::logic_error(what_arg)
        {}
        explicit bad_variant_access(char const * what_arg)
          : std::logic_error(what_arg)
        {}
    };

    template<typename T, std::size_t Index>
    struct indexed_element
    {
    private:
        std::add_pointer_t<T> t_;

    public:
        constexpr explicit indexed_element(T & t) noexcept
          : t_(std::addressof(t))
        {}
        constexpr T & get() const noexcept
        {
            return *t_;
        }
    };
    template<typename T, std::size_t Index>
    struct indexed_element<T &&, Index>
    {
    private:
        T * t_;

    public:
        constexpr explicit indexed_element(T && t) noexcept
          : t_(std::addressof(t))
        {}
        constexpr T && get() const noexcept
        {
            return static_cast<T &&>(*t_);
        }
    };
    template<std::size_t Index>
    struct indexed_element<void, Index>
    {
        void get() const noexcept
        {}
    };

    /// \cond
    namespace detail
    {
        struct indexed_element_fn;

        template<typename I, typename S, typename O>
        auto uninitialized_copy(I first, S last, O out) -> CPP_ret(O)( //
            requires(!sized_sentinel_for<S, I>))
        {
            for(; first != last; ++first, ++out)
                ::new((void *)std::addressof(*out)) iter_value_t<O>(*first);
            return out;
        }

        template<typename I, typename S, typename O>
        auto uninitialized_copy(I first, S last, O out) -> CPP_ret(O)( //
            requires sized_sentinel_for<S, I>)
        {
            return std::uninitialized_copy_n(first, (last - first), out);
        }

        template<typename I, typename O>
        O uninitialized_copy(I first, I last, O out)
        {
            return std::uninitialized_copy(first, last, out);
        }

        template<typename T, typename Index>
        struct indexed_datum
        {
        private:
            template<typename, typename>
            friend struct indexed_datum;
            T datum_;

        public:
            CPP_member
            constexpr CPP_ctor(indexed_datum)()(                          //
                noexcept(std::is_nothrow_default_constructible<T>::value) //
                requires default_constructible<T>)
              : datum_{}
            {}
            CPP_template(typename... Ts)(                                      //
                requires constructible_from<T, Ts...> && (sizeof...(Ts) != 0)) //
                constexpr indexed_datum(Ts &&... ts) noexcept(
                    std::is_nothrow_constructible<T, Ts...>::value)
              : datum_(static_cast<Ts &&>(ts)...)
            {}
            template<typename U>
            constexpr CPP_ctor(indexed_datum)(indexed_datum<U, Index> that)( //
                noexcept(std::is_nothrow_constructible<T, U>::value)         //
                requires(!same_as<T, U>) &&
                convertible_to<U, T>)
              : datum_(std::move(that.datum_))
            {}
            constexpr auto ref() noexcept
            {
                return indexed_element<T, Index::value>{datum_};
            }
            constexpr auto ref() const noexcept
            {
                return indexed_element<T const, Index::value>{datum_};
            }
            constexpr T & get() noexcept
            {
                return datum_;
            }
            constexpr T const & get() const noexcept
            {
                return datum_;
            }
        };

        template<typename T, std::size_t N, typename Index>
        struct indexed_datum<T[N], Index>;

        template<typename T, typename Index>
        struct indexed_datum<T &, Index>
        {
        private:
            template<typename, typename>
            friend struct indexed_datum;
            T * t_;

        public:
            constexpr indexed_datum(T & t) noexcept
              : t_(std::addressof(t))
            {}
            constexpr T & get() const noexcept
            {
                return *t_;
            }
            constexpr auto ref() const noexcept
            {
                return indexed_element<T &, Index::value>{*t_};
            }
        };
        template<typename T, typename Index>
        struct indexed_datum<T &&, Index>
        {
        private:
            template<typename, typename>
            friend struct indexed_datum;
            T * t_;

        public:
            constexpr indexed_datum(T && t) noexcept
              : t_(std::addressof(t))
            {}
            constexpr T && get() const noexcept
            {
                return static_cast<T &&>(*t_);
            }
            constexpr auto ref() const noexcept
            {
                return indexed_element<T &&, Index::value>{static_cast<T &&>(*t_)};
            }
        };
        template<typename Index>
        struct indexed_datum<void, Index>
        {
            void get() const noexcept
            {}
            constexpr indexed_element<void, Index::value> ref() const noexcept
            {
                return {};
            }
        };

        template<std::size_t Index, typename... Ts>
        using variant_datum_t =
            detail::indexed_datum<meta::at_c<meta::list<Ts...>, Index>,
                                  meta::size_t<Index>>;
    } // namespace detail

    template<std::size_t N, typename... Ts, typename... Args>
    meta::if_c<(bool)constructible_from<detail::variant_datum_t<N, Ts...>, Args...>>
    emplace(variant<Ts...> &, Args &&...);

    namespace detail
    {
        using variant_nil = indexed_datum<void, meta::npos>;

        template<typename Ts,
                 bool Trivial = meta::apply<
                     meta::quote<meta::and_>,
                     meta::transform<Ts, meta::quote<std::is_trivially_destructible>>>::
                     type::value>
        struct variant_data_
        {
            using type = indexed_datum<void, meta::npos>;
        };

        template<typename T, typename... Ts>
        struct variant_data_<meta::list<T, Ts...>, true>
        {
            struct type
            {
                using head_t = T;
                using tail_t = meta::_t<variant_data_<meta::list<Ts...>>>;
                union
                {
                    head_t head;
                    tail_t tail;
                };

                type() noexcept
                {}
                template<typename... Args>
                constexpr type(meta::size_t<0>, Args &&... args) noexcept(
                    std::is_nothrow_constructible<head_t, Args...>::value)
                  : head{((Args &&) args)...}
                {}
                template<std::size_t N, typename... Args>
                constexpr type(meta::size_t<N>, Args &&... args) noexcept(
                    std::is_nothrow_constructible<tail_t, meta::size_t<N - 1>,
                                                  Args...>::value)
                  : tail{meta::size_t<N - 1>{}, ((Args &&) args)...}
                {}
            };
        };

        template<typename T, typename... Ts>
        struct variant_data_<meta::list<T, Ts...>, false>
        {
            struct type
            {
                using head_t = T;
                using tail_t = meta::_t<variant_data_<meta::list<Ts...>>>;
                union
                {
                    head_t head;
                    tail_t tail;
                };

                type() noexcept
                {}
                ~type()
                {}
                template<typename... Args>
                constexpr type(meta::size_t<0>, Args &&... args) noexcept(
                    std::is_nothrow_constructible<head_t, Args...>::value)
                  : head{((Args &&) args)...}
                {}
                template<std::size_t N, typename... Args>
                constexpr type(meta::size_t<N>, Args &&... args) noexcept(
                    std::is_nothrow_constructible<tail_t, meta::size_t<N - 1>,
                                                  Args...>::value)
                  : tail{meta::size_t<N - 1>{}, ((Args &&) args)...}
                {}
            };
        };

        template<typename... Ts>
        using variant_data = meta::_t<variant_data_<meta::transform<
            meta::list<Ts...>, meta::as_list<meta::make_index_sequence<sizeof...(Ts)>>,
            meta::quote<indexed_datum>>>>;

        inline std::size_t variant_move_copy_(std::size_t, variant_nil, variant_nil)
        {
            return 0;
        }
        template<typename Data0, typename Data1>
        std::size_t variant_move_copy_(std::size_t n, Data0 & self, Data1 && that)
        {
            using Head = typename Data0::head_t;
            return 0 == n
                       ? ((void)::new((void *)&self.head) Head(((Data1 &&) that).head), 0)
                       : variant_move_copy_(n - 1, self.tail, ((Data1 &&) that).tail) + 1;
        }
        constexpr bool variant_equal_(std::size_t, variant_nil, variant_nil)
        {
            return true;
        }
        template<typename Data0, typename Data1>
        constexpr bool variant_equal_(std::size_t n, Data0 const & self,
                                      Data1 const & that)
        {
            return n == 0 ? self.head.get() == that.head.get()
                          : variant_equal_(n - 1, self.tail, that.tail);
        }
        template<typename Fun, typename Proj = indexed_element_fn>
        constexpr int variant_visit_(std::size_t, variant_nil, Fun, Proj = {})
        {
            return (RANGES_EXPECT(false), 0);
        }
        template<typename Data, typename Fun, typename Proj = indexed_element_fn>
        constexpr int variant_visit_(std::size_t n, Data & self, Fun fun, Proj proj = {})
        {
            return 0 == n ? ((void)invoke(fun, invoke(proj, self.head)), 0)
                          : detail::variant_visit_(
                                n - 1, self.tail, detail::move(fun), detail::move(proj));
        }

        struct get_datum_fn
        {
            template<typename T>
            decltype(auto) operator()(T && t) const noexcept
            {
                return t.get();
            }
        };

        struct indexed_element_fn
        {
            template<typename T>
            decltype(auto) operator()(T && t) const noexcept
            {
                return t.ref();
            }
        };

        struct empty_variant_tag
        {};

        struct variant_core_access
        {
            template<typename... Ts>
            static constexpr variant_data<Ts...> & data(variant<Ts...> & var) noexcept
            {
                return var.data_();
            }
            template<typename... Ts>
            static constexpr variant_data<Ts...> const & data(
                variant<Ts...> const & var) noexcept
            {
                return var.data_();
            }
            template<typename... Ts>
            static constexpr variant_data<Ts...> && data(variant<Ts...> && var) noexcept
            {
                return detail::move(var.data_());
            }
            template<typename... Ts>
            static variant<Ts...> make_empty(meta::id<variant<Ts...>> = {}) noexcept
            {
                return variant<Ts...>{empty_variant_tag{}};
            }
        };

        struct delete_fn
        {
            template<typename T>
            void operator()(T const & t) const noexcept
            {
                t.~T();
            }
        };

        template<std::size_t N, typename... Ts>
        struct construct_fn
        {
            std::tuple<Ts...> args_;

            template<typename U, std::size_t... Is>
            void construct_(U & u, meta::index_sequence<Is...>) noexcept(
                std::is_nothrow_constructible<U, Ts...>::value)
            {
                ::new((void *)std::addressof(u))
                    U(static_cast<Ts &&>(std::get<Is>(args_))...);
            }

            construct_fn(Ts &&... ts) noexcept(
                std::is_nothrow_constructible<std::tuple<Ts...>, Ts...>::value)
              : args_{static_cast<Ts &&>(ts)...}
            {}
            template<typename U, std::size_t M>
            [[noreturn]] meta::if_c<N != M> operator()(
                indexed_datum<U, meta::size_t<M>> &) noexcept
            {
                RANGES_EXPECT(false);
            }
            template<typename U>
            meta::if_<std::is_object<U>> operator()(
                indexed_datum<U, meta::size_t<N>> &
                    u) noexcept(std::is_nothrow_constructible<U, Ts...>::value)
            {
                this->construct_(u.get(), meta::make_index_sequence<sizeof...(Ts)>{});
            }
            template<typename U>
            meta::if_<meta::not_<std::is_object<U>>> operator()(
                indexed_datum<U, meta::size_t<N>> &
                    u) noexcept(std::is_nothrow_constructible<detail::decay_t<U>,
                                                              Ts...>::value)
            {
                this->construct_(u, meta::make_index_sequence<sizeof...(Ts)>{});
            }
        };

        template<typename T, std::size_t N>
        struct get_fn
        {
            T ** t_;

            template<typename U, std::size_t M>
            [[noreturn]] meta::if_c<M != N> operator()(indexed_element<U, M>) const
            {
                throw bad_variant_access("bad variant access");
            }
            template<typename U>
            void operator()(indexed_element<U, N> t) const noexcept
            {
                *t_ = std::addressof(t.get());
            }
            template<typename U>
            void operator()(indexed_element<U &&, N> t) const noexcept
            {
                U && u = t.get();
                *t_ = std::addressof(u);
            }
            void operator()(indexed_element<void, N>) const noexcept
            {}
        };

        template<typename Variant, std::size_t N>
        struct emplace_fn
        {
            Variant * var_;
            // clang-format off
            template<typename...Ts>
            auto CPP_auto_fun(operator())(Ts &&...ts) (const)
            (
                return ranges::emplace<N>(*var_, static_cast<Ts &&>(ts)...)
            )
            // clang-format on
        };

        template<typename Fun, typename Variant>
        struct variant_visitor
        {
            Fun fun_;
            Variant * var_;

            // clang-format off
            template<typename U, std::size_t N>
            auto CPP_auto_fun(operator())(indexed_element<U, N> u)
            (
                return compose(emplace_fn<Variant, N>{var_}, fun_)(u)
            )
            // clang-format on
        };

        template<typename Variant, typename Fun>
        variant_visitor<Fun, Variant> make_variant_visitor(
            Variant & var,
            Fun fun) noexcept(std::is_nothrow_move_constructible<Fun>::value)
        {
            return {detail::move(fun), &var};
        }

        template<typename To, typename From>
        struct unique_visitor;

        template<typename... To, typename... From>
        struct unique_visitor<variant<To...>, variant<From...>>
        {
            variant<To...> * var_;

            template<typename T, std::size_t N>
            void operator()(indexed_element<T, N> t) const
            {
                using E = meta::at_c<meta::list<From...>, N>;
                static_assert(RANGES_IS_SAME(T const, E const),
                              "Is indexed_element broken?");
                using F = meta::find<meta::list<To...>, E>;
                static constexpr std::size_t M = sizeof...(To) - F::size();
                compose(emplace_fn<variant<To...>, M>{var_}, get_datum_fn{})(t);
            }
        };

        template<typename T>
        constexpr T & variant_deref_(T * t) noexcept
        {
            return *t;
        }
        inline void variant_deref_(void const volatile *) noexcept
        {}

        template<typename Variant>
        struct variant_get
        {
            ////////////////////////////////////////////////////////////////////////////////////////////
            // get
            template<std::size_t N>
            friend meta::_t<
                std::add_lvalue_reference<meta::at_c<meta::as_list<Variant>, N>>>
            get(Variant & var)
            {
                using elem_t = meta::_t<
                    std::remove_reference<meta::at_c<meta::as_list<Variant>, N>>>;
                elem_t * elem = nullptr;
                auto & data_var = detail::variant_core_access::data(var);
                detail::variant_visit_(
                    var.index(), data_var, detail::get_fn<elem_t, N>{&elem});
                return detail::variant_deref_(elem);
            }
            template<std::size_t N>
            friend meta::_t<
                std::add_lvalue_reference<meta::at_c<meta::as_list<Variant>, N> const>>
            get(Variant const & var)
            {
                using elem_t = meta::_t<
                    std::remove_reference<meta::at_c<meta::as_list<Variant>, N> const>>;
                elem_t * elem = nullptr;
                auto & data_var = detail::variant_core_access::data(var);
                detail::variant_visit_(
                    var.index(), data_var, detail::get_fn<elem_t, N>{&elem});
                return detail::variant_deref_(elem);
            }
            template<std::size_t N>
            friend meta::_t<
                std::add_rvalue_reference<meta::at_c<meta::as_list<Variant>, N>>>
            get(Variant && var)
            {
                using elem_t = meta::_t<
                    std::remove_reference<meta::at_c<meta::as_list<Variant>, N>>>;
                elem_t * elem = nullptr;
                auto & data_var = detail::variant_core_access::data(var);
                detail::variant_visit_(
                    var.index(), data_var, detail::get_fn<elem_t, N>{&elem});
                using res_t = meta::_t<
                    std::add_rvalue_reference<meta::at_c<meta::as_list<Variant>, N>>>;
                return static_cast<res_t>(detail::variant_deref_(elem));
            }
        };

        template<typename Variant,
                 bool Trivial = std::is_trivially_destructible<meta::apply<
                     meta::quote<variant_data>, meta::as_list<Variant>>>::value>
        struct variant_base : variant_get<Variant>
        {
            ~variant_base()
            {
                static_cast<Variant *>(this)->clear_();
            }
        };
        template<typename... Ts>
        struct variant_base<variant<Ts...>, true> : variant_get<variant<Ts...>>
        {};

        template<typename Fun, typename Types, typename Indices, typename = void>
        struct variant_visit_results
        {};
        template<typename Fun, typename... Ts, std::size_t... Is>
        struct variant_visit_results<
            Fun, meta::list<Ts...>, meta::index_sequence<Is...>,
            meta::void_<invoke_result_t<Fun &, indexed_element<Ts, Is>>...>>
        {
            using type = variant<invoke_result_t<Fun &, indexed_element<Ts, Is>>...>;
        };
        template<typename Fun, typename... Ts>
        using variant_visit_results_t =
            meta::_t<variant_visit_results<Fun, meta::list<Ts...>,
                                           meta::make_index_sequence<sizeof...(Ts)>>>;
    } // namespace detail
    /// \endcond

    /// \addtogroup group-utility
    /// @{
    template<typename... Ts>
    struct variant
      : private detail::variant_data<Ts...>
      , private detail::variant_base<variant<Ts...>>
    {
    private:
        friend detail::variant_core_access;
        template<typename...>
        friend struct variant;
        friend detail::variant_base<variant, false>;
        template<std::size_t Index>
        using datum_t = detail::variant_datum_t<Index, Ts...>;
        template<typename T>
        using add_const_t = meta::if_<std::is_void<T>, void, T const>;
        using unbox_fn = detail::get_datum_fn;

        detail::variant_data<Ts...> & data_() & noexcept
        {
            return *this;
        }
        detail::variant_data<Ts...> const & data_() const & noexcept
        {
            return *this;
        }
        detail::variant_data<Ts...> && data_() && noexcept
        {
            return static_cast<detail::variant_data<Ts...> &&>(*this);
        }

        std::size_t index_;

        void clear_() noexcept
        {
            if(valid())
            {
                detail::variant_visit_(index_, data_(), detail::delete_fn{}, identity{});
                index_ = (std::size_t)-1;
            }
        }
        template<typename That>
        void assign_(That && that)
        {
            if(that.valid())
                index_ = detail::variant_move_copy_(
                    that.index_, data_(), ((That &&) that).data_());
        }
        constexpr variant(detail::empty_variant_tag) noexcept
          : detail::variant_data<Ts...>{}
          , index_((std::size_t)-1)
        {}
        template<typename... Args>
        static constexpr auto all_convertible_to(int) noexcept -> CPP_ret(bool)( //
            requires(sizeof...(Args) == sizeof...(Ts)))
        {
            return and_v<convertible_to<Args, Ts>...>;
        }
        template<typename... Args>
        static constexpr bool all_convertible_to(long) noexcept
        {
            return false;
        }

    public:
        CPP_member
        constexpr CPP_ctor(variant)()(                                         //
            noexcept(std::is_nothrow_default_constructible<datum_t<0>>::value) //
            requires default_constructible<datum_t<0>>)
          : variant{emplaced_index<0>}
        {}
        CPP_template(std::size_t N, typename... Args)(        //
            requires constructible_from<datum_t<N>, Args...>) //
            constexpr variant(emplaced_index_t<N>, Args &&... args) noexcept(
                std::is_nothrow_constructible<datum_t<N>, Args...>::value)
          : detail::variant_data<Ts...>{meta::size_t<N>{}, static_cast<Args &&>(args)...}
          , index_(N)
        {}
        CPP_template(std::size_t N, typename T, typename... Args)( //
            requires constructible_from<datum_t<N>, std::initializer_list<T> &,
                                        Args...>) //
            constexpr variant(
                emplaced_index_t<N>, std::initializer_list<T> il,
                Args &&... args) noexcept(std::
                                              is_nothrow_constructible<
                                                  datum_t<N>, std::initializer_list<T> &,
                                                  Args...>::value)
          : detail::variant_data<Ts...>{meta::size_t<N>{},
                                        il,
                                        static_cast<Args &&>(args)...}
          , index_(N)
        {}
        template<std::size_t N>
        constexpr CPP_ctor(variant)(emplaced_index_t<N>, meta::nil_)(              //
            noexcept(std::is_nothrow_constructible<datum_t<N>, meta::nil_>::value) //
            requires constructible_from<datum_t<N>, meta::nil_>)
          : detail::variant_data<Ts...>{meta::size_t<N>{}, meta::nil_{}}
          , index_(N)
        {}
        variant(variant && that)
          : detail::variant_data<Ts...>{}
          , index_(detail::variant_move_copy_(that.index(), data_(),
                                              std::move(that.data_())))
        {}
        variant(variant const & that)
          : detail::variant_data<Ts...>{}
          , index_(detail::variant_move_copy_(that.index(), data_(), that.data_()))
        {}
        template<typename... Args>
        CPP_ctor(variant)(variant<Args...> that)( //
            requires(!same_as<variant<Args...>, variant>) &&
            (all_convertible_to<Args...>(0)))
          : detail::variant_data<Ts...>{}
          , index_(detail::variant_move_copy_(that.index(), data_(),
                                              std::move(that.data_())))
        {}
        variant & operator=(variant && that)
        {
            // TODO do a simple move assign when index()==that.index()
            this->clear_();
            this->assign_(detail::move(that));
            return *this;
        }
        variant & operator=(variant const & that)
        {
            // TODO do a simple copy assign when index()==that.index()
            this->clear_();
            this->assign_(that);
            return *this;
        }
        template<typename... Args>
        auto operator=(variant<Args...> that) -> CPP_ret(variant &)( //
            requires(!same_as<variant<Args...>, variant>) &&
            (all_convertible_to<Args...>(0)))
        {
            // TODO do a simple copy assign when index()==that.index()
            this->clear_();
            this->assign_(that);
            return *this;
        }
        static constexpr std::size_t size() noexcept
        {
            return sizeof...(Ts);
        }
        template<std::size_t N, typename... Args>
        auto emplace(Args &&... args) -> CPP_ret(void)( //
            requires constructible_from<datum_t<N>, Args...>)
        {
            this->clear_();
            detail::construct_fn<N, Args &&...> fn{static_cast<Args &&>(args)...};
            detail::variant_visit_(N, data_(), std::ref(fn), identity{});
            index_ = N;
        }
        constexpr bool valid() const noexcept
        {
            return index() != (std::size_t)-1;
        }
        constexpr std::size_t index() const noexcept
        {
            return index_;
        }
        template<typename Fun>
        detail::variant_visit_results_t<composed<Fun, unbox_fn>, Ts...> visit(Fun fun)
        {
            detail::variant_visit_results_t<composed<Fun, unbox_fn>, Ts...> res{
                detail::empty_variant_tag{}};
            detail::variant_visit_(index_,
                                   data_(),
                                   detail::make_variant_visitor(
                                       res, compose(detail::move(fun), unbox_fn{})));
            return res;
        }
        template<typename Fun>
        detail::variant_visit_results_t<composed<Fun, unbox_fn>, add_const_t<Ts>...>
        visit(Fun fun) const
        {
            detail::variant_visit_results_t<composed<Fun, unbox_fn>, add_const_t<Ts>...>
                res{detail::empty_variant_tag{}};
            detail::variant_visit_(index_,
                                   data_(),
                                   detail::make_variant_visitor(
                                       res, compose(detail::move(fun), unbox_fn{})));
            return res;
        }
        template<typename Fun>
        detail::variant_visit_results_t<Fun, Ts...> visit_i(Fun fun)
        {
            detail::variant_visit_results_t<Fun, Ts...> res{detail::empty_variant_tag{}};
            detail::variant_visit_(
                index_, data_(), detail::make_variant_visitor(res, detail::move(fun)));
            return res;
        }
        template<typename Fun>
        detail::variant_visit_results_t<Fun, add_const_t<Ts>...> visit_i(Fun fun) const
        {
            detail::variant_visit_results_t<Fun, add_const_t<Ts>...> res{
                detail::empty_variant_tag{}};
            detail::variant_visit_(
                index_, data_(), detail::make_variant_visitor(res, detail::move(fun)));
            return res;
        }
    };

    template<typename... Ts, typename... Us>
    auto operator==(variant<Ts...> const & lhs, variant<Us...> const & rhs)
        -> CPP_ret(bool)( //
            requires and_v<equality_comparable_with<Ts, Us>...>)
    {
        return (!lhs.valid() && !rhs.valid()) ||
               (lhs.index() == rhs.index() &&
                detail::variant_equal_(lhs.index(),
                                       detail::variant_core_access::data(lhs),
                                       detail::variant_core_access::data(rhs)));
    }

    template<typename... Ts, typename... Us>
    auto operator!=(variant<Ts...> const & lhs, variant<Us...> const & rhs)
        -> CPP_ret(bool)( //
            requires and_v<equality_comparable_with<Ts, Us>...>)
    {
        return !(lhs == rhs);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // emplace
    template<std::size_t N, typename... Ts, typename... Args>
    meta::if_c<(bool)constructible_from<detail::variant_datum_t<N, Ts...>, Args...>>
    emplace(variant<Ts...> & var, Args &&... args)
    {
        var.template emplace<N>(static_cast<Args &&>(args)...);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // variant_unique
    template<typename Var>
    struct variant_unique
    {};

    template<typename... Ts>
    struct variant_unique<variant<Ts...>>
    {
        using type = meta::apply<meta::quote<variant>, meta::unique<meta::list<Ts...>>>;
    };

    template<typename Var>
    using variant_unique_t = meta::_t<variant_unique<Var>>;

    ////////////////////////////////////////////////////////////////////////////////////////////
    // unique_variant
    template<typename... Ts>
    variant_unique_t<variant<Ts...>> unique_variant(variant<Ts...> const & var)
    {
        using From = variant<Ts...>;
        using To = variant_unique_t<From>;
        auto res = detail::variant_core_access::make_empty(meta::id<To>{});
        var.visit_i(detail::unique_visitor<To, From>{&res});
        RANGES_EXPECT(res.valid());
        return res;
    }
    /// @}
} // namespace ranges

RANGES_DIAGNOSTIC_PUSH
RANGES_DIAGNOSTIC_IGNORE_MISMATCHED_TAGS

namespace std
{
    template<typename... Ts>
    struct tuple_size<::ranges::variant<Ts...>> : tuple_size<tuple<Ts...>>
    {};

    template<size_t I, typename... Ts>
    struct tuple_element<I, ::ranges::variant<Ts...>> : tuple_element<I, tuple<Ts...>>
    {};
} // namespace std

RANGES_DIAGNOSTIC_POP

#include <range/v3/detail/reenable_warnings.hpp>

#endif
