/// \file
// Range v3 library
//
//  Copyright Eric Niebler 2013-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_UTILITY_COMMON_TUPLE_HPP
#define RANGES_V3_UTILITY_COMMON_TUPLE_HPP

#include <utility>

#include <meta/meta.hpp>

#include <concepts/concepts.hpp>

#include <range/v3/range_fwd.hpp>

#include <range/v3/detail/adl_get.hpp>
#include <range/v3/functional/bind.hpp>
#include <range/v3/functional/reference_wrapper.hpp>
#include <range/v3/utility/common_type.hpp>
#include <range/v3/utility/tuple_algorithm.hpp>

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

namespace ranges
{
    /// \cond
    namespace detail
    {
        template<typename... Us, typename Tup, std::size_t... Is>
        std::tuple<Us...> to_std_tuple(Tup && tup, meta::index_sequence<Is...>)
        {
            return std::tuple<Us...>{adl_get<Is>(static_cast<Tup &&>(tup))...};
        }

#ifdef RANGES_WORKAROUND_MSVC_786312
        template<std::size_t, typename...>
        struct args_;

        template<typename, typename>
        inline constexpr bool argstructible = false;
        template<std::size_t N, typename... Ts, typename... Us>
        inline constexpr bool argstructible<args_<N, Ts...>, args_<N, Us...>> =
            (META_IS_CONSTRUCTIBLE(Ts, Us) && ...);

        template<typename, typename>
        inline constexpr bool argsignable = false;
        template<std::size_t N, typename... Ts, typename... Us>
        inline constexpr bool argsignable<args_<N, Ts...>, args_<N, Us...>> =
            (std::is_assignable_v<Ts &, Us> && ...);
#endif // RANGES_WORKAROUND_MSVC_786312

        template<std::size_t N, typename... Ts>
        struct args_
        {
            template<typename... Us>
            args_(args_<N, Us...>, meta::if_c<
#ifdef RANGES_WORKAROUND_MSVC_786312
                                       argstructible<args_, args_<N, Us...>>
#else  // ^^^ workaround / no workaround vvv
                                       meta::and_c<META_IS_CONSTRUCTIBLE(Ts,
                                                                         Us)...>::value
#endif // RANGES_WORKAROUND_MSVC_786312
                                       > * = nullptr)
            {}
            template<typename... Us>
            meta::if_c<
#ifdef RANGES_WORKAROUND_MSVC_786312
                argsignable<args_, args_<N, Us...>>,
#else  // ^^^ workaround / no workaround vvv
                meta::and_c<std::is_assignable<Ts &, Us>::value...>::value,
#endif // RANGES_WORKAROUND_MSVC_786312
                args_ &>
            operator=(args_<N, Us...>)
            {
                return *this;
            }
        };
        template<typename... Ts>
        using args = args_<sizeof...(Ts), Ts...>;
        template<typename... Ts>
        using rargs = args_<sizeof...(Ts), Ts &...>;
    } // namespace detail
    /// \endcond

    template<typename... Ts>
    struct common_tuple : _tuple_wrapper_::forward_tuple_interface<std::tuple<Ts...>>
    {
    private:
        template<typename That, std::size_t... Is>
        common_tuple(That && that, meta::index_sequence<Is...>)
          : common_tuple::forward_tuple_interface{
                detail::adl_get<Is>(static_cast<That &&>(that))...}
        {}
        struct element_assign_
        {
            template<typename T, typename U>
            int operator()(T & t, U && u) const
            {
                t = static_cast<U &&>(u);
                return 0;
            }
        };

    public:
        // Construction
        CPP_member
        CPP_ctor(common_tuple)()( //
            noexcept(meta::and_c<
                     std::is_nothrow_default_constructible<Ts>::value...>::value) //
            requires default_constructible<std::tuple<Ts...>>)
          : common_tuple::forward_tuple_interface{}
        {}
        CPP_template(typename... Us)(                                              //
            requires constructible_from<detail::args<Ts...>, detail::args<Us...>>) //
            explicit common_tuple(Us &&... us) noexcept(
                meta::and_c<std::is_nothrow_constructible<Ts, Us>::value...>::value)
          : common_tuple::forward_tuple_interface{static_cast<Us &&>(us)...}
        {}
        template<typename... Us>
        CPP_ctor(common_tuple)(std::tuple<Us...> & that)( //
            noexcept(
                meta::and_c<std::is_nothrow_constructible<Ts, Us &>::value...>::value) //
            requires constructible_from<detail::args<Ts...>, detail::rargs<Us...>>)
          : common_tuple(that, meta::make_index_sequence<sizeof...(Ts)>{})
        {}
        template<typename... Us>
        CPP_ctor(common_tuple)(std::tuple<Us...> const & that)( //
            noexcept(meta::and_c<
                     std::is_nothrow_constructible<Ts, Us const &>::value...>::value) //
            requires constructible_from<detail::args<Ts...>, detail::rargs<Us const...>>)
          : common_tuple(that, meta::make_index_sequence<sizeof...(Ts)>{})
        {}
        template<typename... Us>
        CPP_ctor(common_tuple)(std::tuple<Us...> && that)( //
            noexcept(
                meta::and_c<std::is_nothrow_constructible<Ts, Us>::value...>::value) //
            requires constructible_from<detail::args<Ts...>, detail::args<Us...>>)
          : common_tuple(std::move(that), meta::make_index_sequence<sizeof...(Ts)>{})
        {}
        template<typename... Us>
        CPP_ctor(common_tuple)(common_tuple<Us...> & that)( //
            noexcept(
                meta::and_c<std::is_nothrow_constructible<Ts, Us &>::value...>::value) //
            requires constructible_from<detail::args<Ts...>, detail::rargs<Us...>>)
          : common_tuple(that, meta::make_index_sequence<sizeof...(Ts)>{})
        {}
        template<typename... Us>
        CPP_ctor(common_tuple)(common_tuple<Us...> const & that)( //
            noexcept(meta::and_c<
                     std::is_nothrow_constructible<Ts, Us const &>::value...>::value) //
            requires constructible_from<detail::args<Ts...>, detail::rargs<Us const...>>)
          : common_tuple(that, meta::make_index_sequence<sizeof...(Ts)>{})
        {}
        template<typename... Us>
        CPP_ctor(common_tuple)(common_tuple<Us...> && that)( //
            noexcept(
                meta::and_c<std::is_nothrow_constructible<Ts, Us>::value...>::value) //
            requires constructible_from<detail::args<Ts...>, detail::args<Us...>>)
          : common_tuple(std::move(that), meta::make_index_sequence<sizeof...(Ts)>{})
        {}

        std::tuple<Ts...> & base() noexcept
        {
            return *this;
        }
        std::tuple<Ts...> const & base() const noexcept
        {
            return *this;
        }

        // Assignment
        template<typename... Us>
        auto operator=(std::tuple<Us...> & that) noexcept(
            meta::and_c<std::is_nothrow_assignable<Ts &, Us &>::value...>::value)
            -> CPP_ret(common_tuple &)( //
                requires assignable_from<detail::args<Ts...> &, detail::rargs<Us...>>)
        {
            (void)tuple_transform(base(), that, element_assign_{});
            return *this;
        }
        template<typename... Us>
        auto operator=(std::tuple<Us...> const & that) noexcept(
            meta::and_c<std::is_nothrow_assignable<Ts &, Us const &>::value...>::value)
            -> CPP_ret(common_tuple &)( //
                requires assignable_from<detail::args<Ts...> &,
                                         detail::rargs<Us const...>>)
        {
            (void)tuple_transform(base(), that, element_assign_{});
            return *this;
        }
        template<typename... Us>
        auto operator=(std::tuple<Us...> && that) noexcept(
            meta::and_c<std::is_nothrow_assignable<Ts &, Us>::value...>::value)
            -> CPP_ret(common_tuple &)( //
                requires assignable_from<detail::args<Ts...> &, detail::args<Us...>>)
        {
            (void)tuple_transform(base(), std::move(that), element_assign_{});
            return *this;
        }

        template<typename... Us>
        auto operator=(std::tuple<Us...> & that) const noexcept(
            meta::and_c<std::is_nothrow_assignable<Ts const &, Us &>::value...>::value)
            -> CPP_ret(common_tuple const &)( //
                requires assignable_from<detail::args<Ts const...> &,
                                         detail::rargs<Us...>>)
        {
            (void)tuple_transform(base(), that, element_assign_{});
            return *this;
        }
        template<typename... Us>
        auto operator=(std::tuple<Us...> const & that) const
            noexcept(meta::and_c<
                     std::is_nothrow_assignable<Ts const &, Us const &>::value...>::value)
                -> CPP_ret(common_tuple const &)( //
                    requires assignable_from<detail::args<Ts const...> &,
                                             detail::rargs<Us const...>>)
        {
            (void)tuple_transform(base(), that, element_assign_{});
            return *this;
        }
        template<typename... Us>
        auto operator=(std::tuple<Us...> && that) const noexcept(
            meta::and_c<std::is_nothrow_assignable<Ts const &, Us &&>::value...>::value)
            -> CPP_ret(common_tuple const &)( //
                requires assignable_from<detail::args<Ts const...> &,
                                         detail::args<Us...>>)
        {
            (void)tuple_transform(base(), std::move(that), element_assign_{});
            return *this;
        }

        // Conversion
        CPP_template(typename... Us)(                                               //
            requires constructible_from<detail::args<Us...>, detail::rargs<Ts...>>) //
            operator std::tuple<Us...>() &
            noexcept(
                meta::and_c<std::is_nothrow_constructible<Us, Ts &>::value...>::value)
        {
            return detail::to_std_tuple<Us...>(
                *this, meta::make_index_sequence<sizeof...(Ts)>{});
        }
        CPP_template(typename... Us)( //
            requires constructible_from<detail::args<Us...>,
                                        detail::rargs<Ts const...>>) //
        operator std::tuple<Us...>() const & noexcept(
            meta::and_c<std::is_nothrow_constructible<Us, Ts const &>::value...>::value)
        {
            return detail::to_std_tuple<Us...>(
                *this, meta::make_index_sequence<sizeof...(Ts)>{});
        }
        CPP_template(typename... Us)(                                              //
            requires constructible_from<detail::args<Us...>, detail::args<Ts...>>) //
            operator std::tuple<Us...>() &&
            noexcept(meta::and_c<std::is_nothrow_constructible<Us, Ts>::value...>::value)
        {
            return detail::to_std_tuple<Us...>(
                std::move(*this), meta::make_index_sequence<sizeof...(Ts)>{});
        }
    };

    // Logical operators
#define LOGICAL_OP(OP, CONCEPT)                                                    \
    template<typename... Ts, typename... Us>                                       \
    auto operator OP(common_tuple<Ts...> const & a, common_tuple<Us...> const & b) \
        ->CPP_ret(bool)(requires and_v<CONCEPT<Ts, Us>...>)                        \
    {                                                                              \
        return a.base() OP b.base();                                               \
    }                                                                              \
    template<typename... Ts, typename... Us>                                       \
    auto operator OP(std::tuple<Ts...> const & a, common_tuple<Us...> const & b)   \
        ->CPP_ret(bool)(requires and_v<CONCEPT<Ts, Us>...>)                        \
    {                                                                              \
        return a OP b.base();                                                      \
    }                                                                              \
    template<typename... Ts, typename... Us>                                       \
    auto operator OP(common_tuple<Ts...> const & a, std::tuple<Us...> const & b)   \
        ->CPP_ret(bool)(requires and_v<CONCEPT<Ts, Us>...>)                        \
    {                                                                              \
        return a.base() OP b;                                                      \
    }                                                                              \
    /**/
    LOGICAL_OP(==, equality_comparable_with)
    LOGICAL_OP(!=, equality_comparable_with)
    LOGICAL_OP(<, totally_ordered_with)
    LOGICAL_OP(<=, totally_ordered_with)
    LOGICAL_OP(>, totally_ordered_with)
    LOGICAL_OP(>=, totally_ordered_with)
#undef LOGICAL_OP

    struct make_common_tuple_fn
    {
        template<typename... Args>
        common_tuple<bind_element_t<Args>...> operator()(Args &&... args) const noexcept(
            meta::and_c<std::is_nothrow_constructible<
                bind_element_t<Args>, unwrap_reference_t<Args>>::value...>::value)
        {
            return common_tuple<bind_element_t<Args>...>{
                unwrap_reference(static_cast<Args &&>(args))...};
        }
    };

    /// \ingroup group-utility
    /// \sa `make_common_tuple_fn`
    RANGES_INLINE_VARIABLE(make_common_tuple_fn, make_common_tuple)

    template<typename F, typename S>
    struct common_pair : std::pair<F, S>
    {
    private:
        std::pair<F, S> const & base() const noexcept
        {
            return *this;
        }

    public:
        // Construction
        CPP_member
        CPP_ctor(common_pair)()( //
            noexcept(std::is_nothrow_default_constructible<F>::value &&
                         std::is_nothrow_default_constructible<S>::value) //
            requires default_constructible<F> && default_constructible<S>)
          : std::pair<F, S>{}
        {}
        template<typename F2, typename S2>
        CPP_ctor(common_pair)(F2 && f2, S2 && s2)( //
            noexcept(std::is_nothrow_constructible<F, F2>::value &&
                         std::is_nothrow_constructible<S, S2>::value) //
            requires constructible_from<F, F2> && constructible_from<S, S2>)
          : std::pair<F, S>{static_cast<F2 &&>(f2), static_cast<S2 &&>(s2)}
        {}
        template<typename F2, typename S2>
        CPP_ctor(common_pair)(std::pair<F2, S2> & that)( //
            noexcept(std::is_nothrow_constructible<F, F2 &>::value &&
                         std::is_nothrow_constructible<S, S2 &>::value) //
            requires constructible_from<F, F2 &> && constructible_from<S, S2 &>)
          : std::pair<F, S>{that.first, that.second}
        {}
        template<typename F2, typename S2>
        CPP_ctor(common_pair)(std::pair<F2, S2> const & that)( //
            noexcept(std::is_nothrow_constructible<F, F2 const &>::value &&
                         std::is_nothrow_constructible<S, S2 const &>::value) //
            requires constructible_from<F, F2 const &> &&
                constructible_from<S, S2 const &>)
          : std::pair<F, S>{that.first, that.second}
        {}
        template<typename F2, typename S2>
        CPP_ctor(common_pair)(std::pair<F2, S2> && that)( //
            noexcept(std::is_nothrow_constructible<F, F2>::value &&
                         std::is_nothrow_constructible<S, S2>::value) //
            requires constructible_from<F, F2> && constructible_from<S, S2>)
          : std::pair<F, S>{std::forward<F2>(that.first), std::forward<S2>(that.second)}
        {}

        // Conversion
        CPP_template(typename F2, typename S2)(                                  //
            requires constructible_from<F2, F &> && constructible_from<S2, S &>) //
            operator std::pair<F2, S2>() &
            noexcept(std::is_nothrow_constructible<F2, F &>::value &&
                         std::is_nothrow_constructible<S2, S &>::value)
        {
            return {this->first, this->second};
        }
        CPP_template(typename F2, typename S2)( //
            requires constructible_from<F2, F const &> &&
                constructible_from<S2, S const &>) //
        operator std::pair<F2, S2>() const & noexcept(
            std::is_nothrow_constructible<F2, F const &>::value &&
                std::is_nothrow_constructible<S2, S const &>::value)
        {
            return {this->first, this->second};
        }
        CPP_template(typename F2, typename S2)(                              //
            requires constructible_from<F2, F> && constructible_from<S2, S>) //
            operator std::pair<F2, S2>() &&
            noexcept(std::is_nothrow_constructible<F2, F>::value &&
                         std::is_nothrow_constructible<S2, S>::value)
        {
            return {std::forward<F>(this->first), std::forward<S>(this->second)};
        }

        // Assignment
        template<typename F2, typename S2>
        auto operator=(std::pair<F2, S2> & that) noexcept(
            std::is_nothrow_assignable<F &, F2 &>::value &&
                std::is_nothrow_assignable<S &, S2 &>::value)
            -> CPP_ret(common_pair &)( //
                requires assignable_from<F &, F2 &> && assignable_from<S &, S2 &>)
        {
            this->first = that.first;
            this->second = that.second;
            return *this;
        }
        template<typename F2, typename S2>
        auto operator=(std::pair<F2, S2> const & that) noexcept(
            std::is_nothrow_assignable<F &, F2 const &>::value &&
                std::is_nothrow_assignable<S &, S2 const &>::value)
            -> CPP_ret(common_pair &)( //
                requires assignable_from<F &, F2 const &> &&
                    assignable_from<S &, S2 const &>)
        {
            this->first = that.first;
            this->second = that.second;
            return *this;
        }
        template<typename F2, typename S2>
        auto operator=(std::pair<F2, S2> && that) noexcept(
            std::is_nothrow_assignable<F &, F2>::value &&
                std::is_nothrow_assignable<S &, S2>::value) -> CPP_ret(common_pair &)( //
            requires assignable_from<F &, F2> && assignable_from<S &, S2>)
        {
            this->first = static_cast<F2 &&>(that.first);
            this->second = static_cast<S2 &&>(that.second);
            return *this;
        }

        template<typename F2, typename S2>
        auto operator=(std::pair<F2, S2> & that) const
            noexcept(std::is_nothrow_assignable<F const &, F2 &>::value &&
                         std::is_nothrow_assignable<S const &, S2 &>::value)
                -> CPP_ret(common_pair const &)( //
                    requires assignable_from<F const &, F2 &> &&
                        assignable_from<S const &, S2 &>)
        {
            this->first = that.first;
            this->second = that.second;
            return *this;
        }
        template<typename F2, typename S2>
        auto operator=(std::pair<F2, S2> const & that) const
            noexcept(std::is_nothrow_assignable<F const &, F2 const &>::value &&
                         std::is_nothrow_assignable<S const &, S2 const &>::value)
                -> CPP_ret(common_pair const &)( //
                    requires assignable_from<F const &, F2 const &> &&
                        assignable_from<S const &, S2 const &>)
        {
            this->first = that.first;
            this->second = that.second;
            return *this;
        }
        template<typename F2, typename S2>
        auto operator=(std::pair<F2, S2> && that) const noexcept(
            std::is_nothrow_assignable<F const &, F2 &&>::value &&
                std::is_nothrow_assignable<S const &, S2 &&>::value)
            -> CPP_ret(common_pair const &)( //
                requires assignable_from<F const &, F2> && assignable_from<S const &, S2>)
        {
            this->first = static_cast<F2 &&>(that.first);
            this->second = static_cast<S2 &&>(that.second);
            return *this;
        }
    };

    // Logical operators
    template<typename F1, typename S1, typename F2, typename S2>
    auto operator==(common_pair<F1, S1> const & a, common_pair<F2, S2> const & b)
        -> CPP_ret(bool)( //
            requires equality_comparable_with<F1, F2> && equality_comparable_with<S1, S2>)
    {
        return a.first == b.first && a.second == b.second;
    }
    template<typename F1, typename S1, typename F2, typename S2>
    auto operator==(common_pair<F1, S1> const & a, std::pair<F2, S2> const & b)
        -> CPP_ret(bool)( //
            requires equality_comparable_with<F1, F2> && equality_comparable_with<S1, S2>)
    {
        return a.first == b.first && a.second == b.second;
    }
    template<typename F1, typename S1, typename F2, typename S2>
    auto operator==(std::pair<F1, S1> const & a, common_pair<F2, S2> const & b)
        -> CPP_ret(bool)( //
            requires equality_comparable_with<F1, F2> && equality_comparable_with<S1, S2>)
    {
        return a.first == b.first && a.second == b.second;
    }
    template<typename F1, typename S1, typename F2, typename S2>
    auto operator<(common_pair<F1, S1> const & a, common_pair<F2, S2> const & b)
        -> CPP_ret(bool)( //
            requires totally_ordered_with<F1, F2> && totally_ordered_with<S1, S2>)
    {
        return a.first < b.first || (!(b.first < a.first) && a.second < b.second);
    }
    template<typename F1, typename S1, typename F2, typename S2>
    auto operator<(std::pair<F1, S1> const & a, common_pair<F2, S2> const & b)
        -> CPP_ret(bool)( //
            requires totally_ordered_with<F1, F2> && totally_ordered_with<S1, S2>)
    {
        return a.first < b.first || (!(b.first < a.first) && a.second < b.second);
    }
    template<typename F1, typename S1, typename F2, typename S2>
    auto operator<(common_pair<F1, S1> const & a, std::pair<F2, S2> const & b)
        -> CPP_ret(bool)( //
            requires totally_ordered_with<F1, F2> && totally_ordered_with<S1, S2>)
    {
        return a.first < b.first || (!(b.first < a.first) && a.second < b.second);
    }
#define LOGICAL_OP(OP, CONCEPT, RET)                                               \
    template<typename F1, typename S1, typename F2, typename S2>                   \
    auto operator OP(common_pair<F1, S1> const & a, common_pair<F2, S2> const & b) \
        ->CPP_ret(bool)(requires CONCEPT<F1, F2> && CONCEPT<S1, S2>)               \
    {                                                                              \
        return RET;                                                                \
    }                                                                              \
    template<typename F1, typename S1, typename F2, typename S2>                   \
    auto operator OP(std::pair<F1, S1> const & a, common_pair<F2, S2> const & b)   \
        ->CPP_ret(bool)(requires CONCEPT<F1, F2> && CONCEPT<S1, S2>)               \
    {                                                                              \
        return RET;                                                                \
    }                                                                              \
    template<typename F1, typename S1, typename F2, typename S2>                   \
    auto operator OP(common_pair<F1, S1> const & a, std::pair<F2, S2> const & b)   \
        ->CPP_ret(bool)(requires CONCEPT<F1, F2> && CONCEPT<S1, S2>)               \
    {                                                                              \
        return RET;                                                                \
    }                                                                              \
    /**/
    LOGICAL_OP(!=, equality_comparable_with, !(a == b))
    LOGICAL_OP(<=, totally_ordered_with, !(b < a))
    LOGICAL_OP(>, totally_ordered_with, (b < a))
    LOGICAL_OP(>=, totally_ordered_with, !(a < b))
#undef LOGICAL_OP

    struct make_common_pair_fn
    {
        template<typename First, typename Second, typename F = bind_element_t<First>,
                 typename S = bind_element_t<Second>>
        common_pair<F, S> operator()(First && f, Second && s) const noexcept(
            std::is_nothrow_constructible<F, unwrap_reference_t<First>>::value &&
                std::is_nothrow_constructible<F, unwrap_reference_t<Second>>::value)
        {
            return {unwrap_reference(static_cast<First &&>(f)),
                    unwrap_reference(static_cast<Second &&>(s))};
        }
    };

    /// \ingroup group-utility
    /// \sa `make_common_pair_fn`
    RANGES_INLINE_VARIABLE(make_common_pair_fn, make_common_pair)

    /// \cond
    namespace detail
    {
        template<typename, typename, typename, typename = void>
        struct common_type_tuple_like
        {};

        template<template<typename...> class T0, typename... Ts,
                 template<typename...> class T1, typename... Us, typename TupleLike>
        struct common_type_tuple_like<T0<Ts...>, T1<Us...>, TupleLike,
                                      meta::if_c<sizeof...(Ts) == sizeof...(Us)>>
          : meta::lazy::let<
                meta::lazy::invoke<TupleLike, meta::lazy::_t<common_type<Ts, Us>>...>>
        {};

        template<typename T, typename U>
        using make_common_pair =
            meta::if_<meta::or_<std::is_reference<T>, std::is_reference<U>>,
                      common_pair<T, U>, std::pair<T, U>>;

        template<typename... Ts>
        using make_common_tuple =
            meta::if_<meta::any_of<meta::list<Ts...>, meta::quote<std::is_reference>>,
                      common_tuple<Ts...>, std::tuple<Ts...>>;

        template<typename, typename, typename, typename = void>
        struct common_ref_tuple_like
        {};

        template<template<typename...> class T0, typename... Ts,
                 template<typename...> class T1, typename... Us, typename TupleLike>
        struct common_ref_tuple_like<T0<Ts...>, T1<Us...>, TupleLike,
                                     meta::if_c<sizeof...(Ts) == sizeof...(Us)>>
          : meta::lazy::let<meta::lazy::invoke<
                TupleLike, meta::lazy::_t<common_reference<Ts, Us>>...>>
        {};
    } // namespace detail
    /// \endcond
} // namespace ranges

/// \cond
namespace concepts
{
    // common_type for pairs
    template<typename F1, typename S1, typename F2, typename S2>
    struct common_type<std::pair<F1, S1>, ranges::common_pair<F2, S2>>
      : ranges::detail::common_type_tuple_like<
            std::pair<F1, S1>, ranges::common_pair<F2, S2>,
            meta::quote<ranges::detail::make_common_pair>>
    {};
    template<typename F1, typename S1, typename F2, typename S2>
    struct common_type<ranges::common_pair<F1, S1>, std::pair<F2, S2>>
      : ranges::detail::common_type_tuple_like<
            ranges::common_pair<F1, S1>, std::pair<F2, S2>,
            meta::quote<ranges::detail::make_common_pair>>
    {};
    template<typename F1, typename S1, typename F2, typename S2>
    struct common_type<ranges::common_pair<F1, S1>, ranges::common_pair<F2, S2>>
      : ranges::detail::common_type_tuple_like<ranges::common_pair<F1, S1>,
                                               ranges::common_pair<F2, S2>,
                                               meta::quote<ranges::common_pair>>
    {};
    // common_type for tuples
    template<typename... Ts, typename... Us>
    struct common_type<ranges::common_tuple<Ts...>, std::tuple<Us...>>
      : ranges::detail::common_type_tuple_like<
            ranges::common_tuple<Ts...>, std::tuple<Us...>,
            meta::quote<ranges::detail::make_common_tuple>>
    {};
    template<typename... Ts, typename... Us>
    struct common_type<std::tuple<Ts...>, ranges::common_tuple<Us...>>
      : ranges::detail::common_type_tuple_like<
            std::tuple<Ts...>, ranges::common_tuple<Us...>,
            meta::quote<ranges::detail::make_common_tuple>>
    {};
    template<typename... Ts, typename... Us>
    struct common_type<ranges::common_tuple<Ts...>, ranges::common_tuple<Us...>>
      : ranges::detail::common_type_tuple_like<ranges::common_tuple<Ts...>,
                                               ranges::common_tuple<Us...>,
                                               meta::quote<ranges::common_tuple>>
    {};

    // common reference for pairs
    template<typename F1, typename S1, typename F2, typename S2,
             template<typename> class Qual1, template<typename> class Qual2>
    struct basic_common_reference<ranges::common_pair<F1, S1>, std::pair<F2, S2>, Qual1,
                                  Qual2>
      : ranges::detail::common_ref_tuple_like<
            ranges::common_pair<Qual1<F1>, Qual1<S1>>, std::pair<Qual2<F2>, Qual2<S2>>,
            meta::quote<ranges::detail::make_common_pair>>
    {};
    template<typename F1, typename S1, typename F2, typename S2,
             template<typename> class Qual1, template<typename> class Qual2>
    struct basic_common_reference<std::pair<F1, S1>, ranges::common_pair<F2, S2>, Qual1,
                                  Qual2>
      : ranges::detail::common_ref_tuple_like<
            std::pair<Qual1<F1>, Qual1<S1>>, ranges::common_pair<Qual2<F2>, Qual2<S2>>,
            meta::quote<ranges::detail::make_common_pair>>
    {};
    template<typename F1, typename S1, typename F2, typename S2,
             template<typename> class Qual1, template<typename> class Qual2>
    struct basic_common_reference<ranges::common_pair<F1, S1>,
                                  ranges::common_pair<F2, S2>, Qual1, Qual2>
      : ranges::detail::common_ref_tuple_like<ranges::common_pair<Qual1<F1>, Qual1<S1>>,
                                              ranges::common_pair<Qual2<F2>, Qual2<S2>>,
                                              meta::quote<ranges::common_pair>>
    {};
    // common reference for tuples
    template<typename... Ts, typename... Us, template<typename> class Qual1,
             template<typename> class Qual2>
    struct basic_common_reference<ranges::common_tuple<Ts...>, std::tuple<Us...>, Qual1,
                                  Qual2>
      : ranges::detail::common_ref_tuple_like<
            ranges::common_tuple<Qual1<Ts>...>, std::tuple<Qual2<Us>...>,
            meta::quote<ranges::detail::make_common_tuple>>
    {};
    template<typename... Ts, typename... Us, template<typename> class Qual1,
             template<typename> class Qual2>
    struct basic_common_reference<std::tuple<Ts...>, ranges::common_tuple<Us...>, Qual1,
                                  Qual2>
      : ranges::detail::common_ref_tuple_like<
            std::tuple<Qual1<Ts>...>, ranges::common_tuple<Qual2<Us>...>,
            meta::quote<ranges::detail::make_common_tuple>>
    {};
    template<typename... Ts, typename... Us, template<typename> class Qual1,
             template<typename> class Qual2>
    struct basic_common_reference<ranges::common_tuple<Ts...>,
                                  ranges::common_tuple<Us...>, Qual1, Qual2>
      : ranges::detail::common_ref_tuple_like<ranges::common_tuple<Qual1<Ts>...>,
                                              ranges::common_tuple<Qual2<Us>...>,
                                              meta::quote<ranges::common_tuple>>
    {};
} // namespace concepts
/// \endcond

RANGES_DIAGNOSTIC_PUSH
RANGES_DIAGNOSTIC_IGNORE_MISMATCHED_TAGS

namespace std
{
    template<typename First, typename Second>
    struct tuple_size<::ranges::common_pair<First, Second>>
      : std::integral_constant<size_t, 2>
    {};

    template<typename First, typename Second>
    struct tuple_element<0, ::ranges::common_pair<First, Second>>
    {
        using type = First;
    };

    template<typename First, typename Second>
    struct tuple_element<1, ::ranges::common_pair<First, Second>>
    {
        using type = Second;
    };

    template<typename... Ts>
    struct tuple_size<::ranges::common_tuple<Ts...>>
      : std::integral_constant<size_t, sizeof...(Ts)>
    {};

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

RANGES_DIAGNOSTIC_POP

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

#endif
