/*!
@file
Forward declares `boost::hana::type` and related utilities.

Copyright Louis Dionne 2013-2022
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_FWD_TYPE_HPP
#define BOOST_HANA_FWD_TYPE_HPP

#include <boost/hana/config.hpp>
#include <boost/hana/fwd/core/make.hpp>


namespace boost { namespace hana {
    //! Base class of `hana::type`; used for pattern-matching.
    //! @relates hana::type
    //!
    //! Example
    //! -------
    //! @include example/type/basic_type.cpp
    template <typename T>
    struct basic_type;

    //! @ingroup group-datatypes
    //! C++ type in value-level representation.
    //!
    //! A `type` is a special kind of object representing a C++ type like
    //! `int`, `void`, `std::vector<float>` or anything else you can imagine.
    //!
    //! This page explains how `type`s work at a low level. To gain
    //! intuition about type-level metaprogramming in Hana, you should
    //! read the [tutorial section](@ref tutorial-type) on type-level
    //! computations.
    //!
    //!
    //! @note
    //! For subtle reasons, the actual representation of `hana::type` is
    //! implementation-defined. In particular, `hana::type` may be a dependent
    //! type, so one should not attempt to do pattern matching on it. However,
    //! one can assume that `hana::type` _inherits_ from `hana::basic_type`,
    //! which can be useful when declaring overloaded functions:
    //! @code
    //!     template <typename T>
    //!     void f(hana::basic_type<T>) {
    //!         // do something with T
    //!     }
    //! @endcode
    //! The full story is that [ADL][] causes template arguments to be
    //! instantiated. Hence, if `hana::type` were defined naively, expressions
    //! like `hana::type<T>{} == hana::type<U>{}` would cause both `T` and `U`
    //! to be instantiated. This is usually not a problem, except when `T` or
    //! `U` should not be instantiated. To avoid these instantiations,
    //! `hana::type` is implemented using some cleverness, and that is
    //! why the representation is implementation-defined. When that
    //! behavior is not required, `hana::basic_type` can be used instead.
    //!
    //!
    //! @anchor type_lvalues_and_rvalues
    //! Lvalues and rvalues
    //! -------------------
    //! When storing `type`s in heterogeneous containers, some algorithms
    //! will return references to those objects. Since we are primarily
    //! interested in accessing their nested `::%type`, receiving a reference
    //! is undesirable; we would end up trying to fetch the nested `::%type`
    //! inside a reference type, which is a compilation error:
    //! @code
    //!   auto ts = make_tuple(type_c<int>, type_c<char>);
    //!   using T = decltype(ts[0_c])::type; // error: 'ts[0_c]' is a reference!
    //! @endcode
    //!
    //! For this reason, `type`s provide an overload of the unary `+`
    //! operator that can be used to turn a lvalue into a rvalue. So when
    //! using a result which might be a reference to a `type` object, one
    //! can use `+` to make sure a rvalue is obtained before fetching its
    //! nested `::%type`:
    //! @code
    //!   auto ts = make_tuple(type_c<int>, type_c<char>);
    //!   using T = decltype(+ts[0_c])::type; // ok: '+ts[0_c]' is an rvalue
    //! @endcode
    //!
    //!
    //! Modeled concepts
    //! ----------------
    //! 1. `Comparable`\n
    //! Two types are equal if and only if they represent the same C++ type.
    //! Hence, equality is equivalent to the `std::is_same` type trait.
    //! @include example/type/comparable.cpp
    //!
    //! 2. `Hashable`\n
    //! The hash of a type is just that type itself. In other words, `hash`
    //! is the identity function on `hana::type`s.
    //! @include example/type/hashable.cpp
    //!
    //! [ADL]: http://en.cppreference.com/w/cpp/language/adl
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    template <typename T>
    struct type {
        //! Returns rvalue of self.
        //! See @ref type_lvalues_and_rvalues "description".
        constexpr auto operator+() const;

        //! Equivalent to `hana::equal`
        template <typename X, typename Y>
        friend constexpr auto operator==(X&& x, Y&& y);

        //! Equivalent to `hana::not_equal`
        template <typename X, typename Y>
        friend constexpr auto operator!=(X&& x, Y&& y);
    };
#else
    template <typename T>
    struct type_impl;

    template <typename T>
    using type = typename type_impl<T>::_;
#endif

    //! Tag representing `hana::type`.
    //! @relates hana::type
    struct type_tag { };

    //! Creates an object representing the C++ type `T`.
    //! @relates hana::type
    template <typename T>
    BOOST_HANA_INLINE_VARIABLE constexpr type<T> type_c{};

    //! `decltype` keyword, lifted to Hana.
    //! @relates hana::type
    //!
    //! @deprecated
    //! The semantics of `decltype_` can be confusing, and `hana::typeid_`
    //! should be preferred instead. `decltype_` may be removed in the next
    //! major version of the library.
    //!
    //! `decltype_` is somewhat equivalent to `decltype` in that it returns
    //! the type of an object, except it returns it as a `hana::type` which
    //! is a first-class citizen of Hana instead of a raw C++ type.
    //! Specifically, given an object `x`, `decltype_` satisfies
    //! @code
    //!   decltype_(x) == type_c<decltype(x) with references stripped>
    //! @endcode
    //!
    //! As you can see, `decltype_` will strip any reference from the
    //! object's actual type. The reason for doing so is explained below.
    //! However, any `cv`-qualifiers will be retained. Also, when given a
    //! `hana::type`, `decltype_` is just the identity function. Hence,
    //! for any C++ type `T`,
    //! @code
    //!   decltype_(type_c<T>) == type_c<T>
    //! @endcode
    //!
    //! In conjunction with the way `metafunction` & al. are specified, this
    //! behavior makes it easier to interact with both types and values at
    //! the same time. However, it does make it impossible to create a `type`
    //! containing another `type` with `decltype_`. In other words, it is
    //! not possible to create a `type_c<decltype(type_c<T>)>` with this
    //! utility, because `decltype_(type_c<T>)` would be just `type_c<T>`
    //! instead of `type_c<decltype(type_c<T>)>`. This use case is assumed
    //! to be rare and a hand-coded function can be used if this is needed.
    //!
    //!
    //! ### Rationale for stripping the references
    //! The rules for template argument deduction are such that a perfect
    //! solution that always matches `decltype` is impossible. Hence, we
    //! have to settle on a solution that's good and and consistent enough
    //! for our needs. One case where matching `decltype`'s behavior is
    //! impossible is when the argument is a plain, unparenthesized variable
    //! or function parameter. In that case, `decltype_`'s argument will be
    //! deduced as a reference to that variable, but `decltype` would have
    //! given us the actual type of that variable, without references. Also,
    //! given the current definition of `metafunction` & al., it would be
    //! mostly useless if `decltype_` could return a reference, because it
    //! is unlikely that `F` expects a reference in its simplest use case:
    //! @code
    //!   int i = 0;
    //!   auto result = metafunction<F>(i);
    //! @endcode
    //!
    //! Hence, always discarding references seems to be the least painful
    //! solution.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/decltype.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto decltype_ = see documentation;
#else
    struct decltype_t {
        template <typename T>
        constexpr auto operator()(T&&) const;
    };

    BOOST_HANA_INLINE_VARIABLE constexpr decltype_t decltype_{};
#endif

    //! Returns a `hana::type` representing the type of a given object.
    //! @relates hana::type
    //!
    //! `hana::typeid_` is somewhat similar to `typeid` in that it returns
    //! something that represents the type of an object. However, what
    //! `typeid` returns represent the _runtime_ type of the object, while
    //! `hana::typeid_` returns the _static_ type of the object. Specifically,
    //! given an object `x`, `typeid_` satisfies
    //! @code
    //!   typeid_(x) == type_c<decltype(x) with ref and cv-qualifiers stripped>
    //! @endcode
    //!
    //! As you can see, `typeid_` strips any reference and cv-qualifier from
    //! the object's actual type. The reason for doing so is that it faithfully
    //! models how the language's `typeid` behaves with respect to reference
    //! and cv-qualifiers, and it also turns out to be the desirable behavior
    //! most of the time. Also, when given a `hana::type`, `typeid_` is just
    //! the identity function. Hence, for any C++ type `T`,
    //! @code
    //!   typeid_(type_c<T>) == type_c<T>
    //! @endcode
    //!
    //! In conjunction with the way `metafunction` & al. are specified, this
    //! behavior makes it easier to interact with both types and values at
    //! the same time. However, it does make it impossible to create a `type`
    //! containing another `type` using `typeid_`. This use case is assumed
    //! to be rare and a hand-coded function can be used if this is needed.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/typeid.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto typeid_ = see documentation;
#else
    struct typeid_t {
        template <typename T>
        constexpr auto operator()(T&&) const;
    };

    BOOST_HANA_INLINE_VARIABLE constexpr typeid_t typeid_{};
#endif

#ifdef BOOST_HANA_DOXYGEN_INVOKED
    //! Equivalent to `decltype_`, provided for convenience.
    //! @relates hana::type
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/make.cpp
    template <>
    constexpr auto make<type_tag> = hana::decltype_;
#endif

    //! Equivalent to `make<type_tag>`, provided for convenience.
    //! @relates hana::type
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/make.cpp
    BOOST_HANA_INLINE_VARIABLE constexpr auto make_type = hana::make<type_tag>;

    //! `sizeof` keyword, lifted to Hana.
    //! @relates hana::type
    //!
    //! `sizeof_` is somewhat equivalent to `sizeof` in that it returns the
    //! size of an expression or type, but it takes an arbitrary expression
    //! or a `hana::type` and returns its size as an `integral_constant`.
    //! Specifically, given an expression `expr`, `sizeof_` satisfies
    //! @code
    //!   sizeof_(expr) == size_t<sizeof(decltype(expr) with references stripped)>
    //! @endcode
    //!
    //! However, given a `type`, `sizeof_` will simply fetch the size
    //! of the C++ type represented by that object. In other words,
    //! @code
    //!   sizeof_(type_c<T>) == size_t<sizeof(T)>
    //! @endcode
    //!
    //! The behavior of `sizeof_` is consistent with that of `decltype_`.
    //! In particular, see `decltype_`'s documentation to understand why
    //! references are always stripped by `sizeof_`.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/sizeof.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto sizeof_ = [](auto&& x) {
        using T = typename decltype(hana::decltype_(x))::type;
        return hana::size_c<sizeof(T)>;
    };
#else
    struct sizeof_t {
        template <typename T>
        constexpr auto operator()(T&&) const;
    };

    BOOST_HANA_INLINE_VARIABLE constexpr sizeof_t sizeof_{};
#endif

    //! `alignof` keyword, lifted to Hana.
    //! @relates hana::type
    //!
    //! `alignof_` is somewhat equivalent to `alignof` in that it returns the
    //! alignment required by any instance of a type, but it takes a `type`
    //! and returns its alignment as an `integral_constant`. Like `sizeof`
    //! which works for expressions and type-ids, `alignof_` can also be
    //! called on an arbitrary expression. Specifically, given an expression
    //! `expr` and a C++ type `T`, `alignof_` satisfies
    //! @code
    //!   alignof_(expr) == size_t<alignof(decltype(expr) with references stripped)>
    //!   alignof_(type_c<T>) == size_t<alignof(T)>
    //! @endcode
    //!
    //! The behavior of `alignof_` is consistent with that of `decltype_`.
    //! In particular, see `decltype_`'s documentation to understand why
    //! references are always stripped by `alignof_`.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/alignof.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto alignof_ = [](auto&& x) {
        using T = typename decltype(hana::decltype_(x))::type;
        return hana::size_c<alignof(T)>;
    };
#else
    struct alignof_t {
        template <typename T>
        constexpr auto operator()(T&&) const;
    };

    BOOST_HANA_INLINE_VARIABLE constexpr alignof_t alignof_{};
#endif

    //! Checks whether a SFINAE-friendly expression is valid.
    //! @relates hana::type
    //!
    //! Given a SFINAE-friendly function, `is_valid` returns whether the
    //! function call is valid with the given arguments. Specifically, given
    //! a function `f` and arguments `args...`,
    //! @code
    //!   is_valid(f, args...) == whether f(args...) is valid
    //! @endcode
    //!
    //! The result is returned as a compile-time `Logical`. Furthermore,
    //! `is_valid` can be used in curried form as follows:
    //! @code
    //!   is_valid(f)(args...)
    //! @endcode
    //!
    //! This syntax makes it easy to create functions that check the validity
    //! of a generic expression on any given argument(s).
    //!
    //! @warning
    //! To check whether calling a nullary function `f` is valid, one should
    //! use the `is_valid(f)()` syntax. Indeed, `is_valid(f /* no args */)`
    //! will be interpreted as the currying of `is_valid` to `f` rather than
    //! the application of `is_valid` to `f` and no arguments.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/is_valid.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto is_valid = [](auto&& f) {
        return [](auto&& ...args) {
            return whether f(args...) is a valid expression;
        };
    };
#else
    struct is_valid_t {
        template <typename F>
        constexpr auto operator()(F&&) const;

        template <typename F, typename ...Args>
        constexpr auto operator()(F&&, Args&&...) const;
    };

    BOOST_HANA_INLINE_VARIABLE constexpr is_valid_t is_valid{};
#endif

    //! Lift a template to a Metafunction.
    //! @ingroup group-Metafunction
    //!
    //! Given a template class or template alias `f`, `template_<f>` is a
    //! `Metafunction` satisfying
    //! @code
    //!     template_<f>(type_c<x>...) == type_c<f<x...>>
    //!     decltype(template_<f>)::apply<x...>::type == f<x...>
    //! @endcode
    //!
    //! @note
    //! In a SFINAE context, the expression `template_<f>(type_c<x>...)` is
    //! valid whenever the expression `f<x...>` is valid.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/template.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    template <template <typename ...> class F>
    constexpr auto template_ = [](basic_type<T>...) {
        return hana::type_c<F<T...>>;
    };
#else
    template <template <typename ...> class F>
    struct template_t;

    template <template <typename ...> class F>
    BOOST_HANA_INLINE_VARIABLE constexpr template_t<F> template_{};
#endif

    //! Lift a MPL-style metafunction to a Metafunction.
    //! @ingroup group-Metafunction
    //!
    //! Given a MPL-style metafunction, `metafunction<f>` is a `Metafunction`
    //! satisfying
    //! @code
    //!     metafunction<f>(type_c<x>...) == type_c<f<x...>::type>
    //!     decltype(metafunction<f>)::apply<x...>::type == f<x...>::type
    //! @endcode
    //!
    //! @note
    //! In a SFINAE context, the expression `metafunction<f>(type_c<x>...)` is
    //! valid whenever the expression `f<x...>::%type` is valid.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/metafunction.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    template <template <typename ...> class F>
    constexpr auto metafunction = [](basic_type<T>...) {
        return hana::type_c<typename F<T...>::type>;
    };
#else
    template <template <typename ...> class f>
    struct metafunction_t;

    template <template <typename ...> class f>
    BOOST_HANA_INLINE_VARIABLE constexpr metafunction_t<f> metafunction{};
#endif

    //! Lift a MPL-style metafunction class to a Metafunction.
    //! @ingroup group-Metafunction
    //!
    //! Given a MPL-style metafunction class, `metafunction_class<f>` is a
    //! `Metafunction` satisfying
    //! @code
    //!     metafunction_class<f>(type_c<x>...) == type_c<f::apply<x...>::type>
    //!     decltype(metafunction_class<f>)::apply<x...>::type == f::apply<x...>::type
    //! @endcode
    //!
    //! @note
    //! In a SFINAE context, the expression `metafunction_class<f>(type_c<x>...)`
    //! is valid whenever the expression `f::apply<x...>::%type` is valid.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/metafunction_class.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    template <typename F>
    constexpr auto metafunction_class = [](basic_type<T>...) {
        return hana::type_c<typename F::template apply<T...>::type>;
    };
#else
    template <typename F>
    struct metafunction_class_t;

    template <typename F>
    BOOST_HANA_INLINE_VARIABLE constexpr metafunction_class_t<F> metafunction_class{};
#endif

    //! Turn a `Metafunction` into a function taking `type`s and returning a
    //! default-constructed object.
    //! @ingroup group-Metafunction
    //!
    //! Given a `Metafunction` `f`, `integral` returns a new `Metafunction`
    //! that default-constructs an object of the type returned by `f`. More
    //! specifically, the following holds:
    //! @code
    //!     integral(f)(t...) == decltype(f(t...))::type{}
    //! @endcode
    //!
    //! The principal use case for `integral` is to transform `Metafunction`s
    //! returning a type that inherits from a meaningful base like
    //! `std::integral_constant` into functions returning e.g. a
    //! `hana::integral_constant`.
    //!
    //! @note
    //! - This is not a `Metafunction` because it does not return a `type`.
    //!   As such, it would not make sense to make `decltype(integral(f))`
    //!   a MPL metafunction class like the usual `Metafunction`s are.
    //!
    //! - When using `integral` with metafunctions returning
    //!   `std::integral_constant`s, don't forget to include the
    //!   boost/hana/ext/std/integral_constant.hpp header to ensure
    //!   Hana can interoperate with the result.
    //!
    //! - In a SFINAE context, the expression `integral(f)(t...)` is valid
    //!   whenever the expression `decltype(f(t...))::%type` is valid.
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/integral.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto integral = [](auto f) {
        return [](basic_type<T>...) {
            return decltype(f)::apply<T...>::type{};
        };
    };
#else
    template <typename F>
    struct integral_t;

    struct make_integral_t {
        template <typename F>
        constexpr integral_t<F> operator()(F const&) const
        { return {}; }
    };

    BOOST_HANA_INLINE_VARIABLE constexpr make_integral_t integral{};
#endif

    //! Alias to `integral(metafunction<F>)`, provided for convenience.
    //! @ingroup group-Metafunction
    //!
    //!
    //! Example
    //! -------
    //! @include example/type/trait.cpp
    template <template <typename ...> class F>
    BOOST_HANA_INLINE_VARIABLE constexpr auto trait = hana::integral(hana::metafunction<F>);
}} // end namespace boost::hana

#endif // !BOOST_HANA_FWD_TYPE_HPP
