/* C++ 20 bit cast emulation.
(C) 2018 - 2019 Niall Douglas <http://www.nedproductions.biz/> (3 commits)
File Created: May 2019


Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License in the accompanying file
Licence.txt or at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


Distributed under the Boost Software License, Version 1.0.
    (See accompanying file Licence.txt or copy at
          http://www.boost.org/LICENSE_1_0.txt)
*/

#ifndef QUICKCPPLIB_BIT_CAST_HPP
#define QUICKCPPLIB_BIT_CAST_HPP

#include "config.hpp"

#ifndef QUICKCPPLIB_USE_STD_BIT_CAST
#if __cplusplus >= 202000 && __has_include(<bit>)
#include <bit>
#if __cpp_lib_bit_cast
#define QUICKCPPLIB_USE_STD_BIT_CAST 1
#else
#define QUICKCPPLIB_USE_STD_BIT_CAST 0
#endif
#else
#define QUICKCPPLIB_USE_STD_BIT_CAST 0
#endif
#endif

#include <type_traits>

QUICKCPPLIB_NAMESPACE_BEGIN

namespace bit_cast
{
  //! Namespace for user specialised traits
  namespace traits
  {
    /*! Specialise to true if you guarantee that a type is move relocating (i.e.
    its move constructor equals copying bits from old to new, old is left in a
    default constructed state, and calling the destructor on a default constructed
    instance is trivial). All trivially copyable types are move relocating by
    definition, and that is the unspecialised implementation.
    */
    template <class T> struct is_move_relocating
    {
      static constexpr bool value = std::is_trivially_copyable<T>::value;
    };
  }  // namespace traits

  namespace detail
  {
    template <class T>
    using is_integral_or_enum = std::integral_constant<bool, std::is_integral<T>::value || std::is_enum<T>::value>;

    template <class To, class From>
    using is_static_castable =
    std::integral_constant<bool, is_integral_or_enum<To>::value && is_integral_or_enum<From>::value>;

  }  // namespace detail

#if QUICKCPPLIB_USE_STD_BIT_CAST
  using std::bit_cast;
#else
  namespace detail
  {
    /* A partially compliant implementation of C++20's std::bit_cast function contributed
    by Jesse Towner.
    Our bit_cast is only guaranteed to be constexpr when both the input and output
    arguments are either integrals or enums. We still attempt a constexpr union-based
    type pun for non-array input types, which some compilers accept. For array inputs,
    we fall back to non-constexpr memmove.
    */

    template <class To, class From>
    using is_union_castable = std::integral_constant<bool, !is_static_castable<To, From>::value &&
                                                           !std::is_array<To>::value && !std::is_array<From>::value>;

    template <class To, class From>
    using is_bit_castable =
    std::integral_constant<bool, sizeof(To) == sizeof(From) && traits::is_move_relocating<To>::value &&
                                 traits::is_move_relocating<From>::value>;

    template <class To, class From> union bit_cast_union
    {
      From source;
      To target;
    };

    struct static_cast_overload
    {
    };
    struct union_cast_overload
    {
    };
    struct memmove_overload
    {
    };
  }  // namespace detail

  /*! \brief Bit cast emulation chosen if types are move relocating or trivally copyable,
  have identical size, and are statically castable. Constexpr.
  */
  QUICKCPPLIB_TEMPLATE(class To, class From)
  QUICKCPPLIB_TREQUIRES(QUICKCPPLIB_TPRED(detail::is_bit_castable<To, From>::value       //
                                          &&detail::is_static_castable<To, From>::value  //
                                          && !detail::is_union_castable<To, From>::value))
  constexpr inline To bit_cast(const From &from, detail::static_cast_overload = {}) noexcept
  {
    return static_cast<To>(from);
  }

  /*! \brief Bit cast emulation chosen if types are move relocating or trivally copyable,
  have identical size, and are union castable. Constexpr.
  */
  QUICKCPPLIB_TEMPLATE(class To, class From)
  QUICKCPPLIB_TREQUIRES(QUICKCPPLIB_TPRED(detail::is_bit_castable<To, From>::value         //
                                          && !detail::is_static_castable<To, From>::value  //
                                          && detail::is_union_castable<To, From>::value))
  constexpr inline To bit_cast(const From &from, detail::union_cast_overload = {}) noexcept
  {
    return detail::bit_cast_union<To, From>{from}.target;
  }

  /*! \brief Bit cast emulation chosen if an array of types which are move relocating or
  trivally copyable, and have identical size. NOT constexpr.
  */
  QUICKCPPLIB_TEMPLATE(class To, class From)
  QUICKCPPLIB_TREQUIRES(QUICKCPPLIB_TPRED(detail::is_bit_castable<To, From>::value         //
                                          && !detail::is_static_castable<To, From>::value  //
                                          && !detail::is_union_castable<To, From>::value))
  inline To bit_cast(const From &from, detail::memmove_overload = {}) noexcept
  {
    detail::bit_cast_union<To, From> ret;
    memmove(&ret.source, &from, sizeof(ret.source));
    return ret.target;
  }

#endif

}  // namespace bit_cast

QUICKCPPLIB_NAMESPACE_END

#endif
