/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2011-2018 Danny Robson */ #pragma once #include "concepts.hpp" #include "debug/assert.hpp" #include "debug/validate.hpp" #include "platform.hpp" #include #include #include namespace cruft::cast { /////////////////////////////////////////////////////////////////////////// /// Safely cast a numeric type to its (un)signed counterpart, aborting if /// the dynamically checked result is not representable. May be optimised /// out if NDEBUG is defined. /// /// The signed/unsigned and unsigned/signed cases are split so we can /// simplify the out of range tests. /// /// The same-type case is not provided because we want to error out on /// unnecessary casts. template < typename T, typename U > std::enable_if_t< sizeof(T) == sizeof(U) && std::is_unsigned::value && std::is_signed::value, T > sign (const U u) { CHECK_GE (u, 0); return static_cast (u); } //------------------------------------------------------------------------- template < typename T, typename U > std::enable_if_t< sizeof(T) == sizeof (U) && std::is_signed::value && std::is_unsigned::value, T > sign (const U u) { CHECK_LT (u, std::numeric_limits::max () / 2); return static_cast (u); } /////////////////////////////////////////////////////////////////////////// /// Cast to a smaller type of the same signedness and realness and assert /// that both values are still equal. /// /// Any runtime checks will be compiled out if NDEBUG is defined. /// /// Identity casts are allowed so as to simplify the use of this routine /// in template code. template < concepts::arithmetic NarrowT, concepts::arithmetic WideT > requires (std::is_signed_v == std::is_signed_v) && (std::is_floating_point_v == std::is_floating_point_v) && (sizeof (NarrowT) <= sizeof (WideT)) constexpr NarrowT narrow (const WideT &val) { static_assert (sizeof (NarrowT) <= sizeof (WideT)); #ifndef NDEBUG auto narrow = static_cast (val); CHECK_EQ (narrow, val); return narrow; #else return static_cast (val); #endif } /////////////////////////////////////////////////////////////////////////// /// Cast between types checking that exact equality holds if the result is /// casted back to the original type. /// /// Runtime checks will be compiled out if NDEBUG is defined. template constexpr DstT lossless (const SrcT &src) { #ifndef NDEBUG // GCC insists that the initial static_cast to DstT is a floating // point comparison if we pass in a bool and a float. // // The only way around this is to ignore the warning locally (we use // almost_equal inside CHECK_EQ anyway, so it should not be a concern). #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" auto dst = static_cast (src); if constexpr (std::is_floating_point_v) { if (std::isnan (src)) { // NaNs must remaing as NaN's. They're important. CHECK (std::is_floating_point_v); CHECK (std::isnan (dst)); } } // Cast dst back to src to check round-trip conversion // is lossless. CHECK_EQ (static_cast (dst), src); #pragma GCC diagnostic pop return dst; #else #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshorten-64-to-32" if constexpr (std::is_enum_v) { auto const val = static_cast> (src); return static_cast (val); } else { return static_cast (src); } #pragma GCC diagnostic pop #endif } /////////////////////////////////////////////////////////////////////////// /// Assert if the value is not a pointer to a subclass of T, else return /// the converted value. Note: this is only a debug-time check and is /// compiled out in optimised builds. template < typename T, typename V > std::enable_if_t,T> known (V *const v) { CHECK (dynamic_cast (v)); return static_cast (v); } //------------------------------------------------------------------------- template < typename T, typename V > std::enable_if_t, T> known (V &v) { CHECK_NOTHROW (dynamic_cast (v)); return reinterpret_cast (v); } /////////////////////////////////////////////////////////////////////////// /// Cast a pointer from one type to another, asserting that the required /// alignment of the destination type has been satisfied. /// /// Runtime checks will be compiled out if NDEBUG is defined. template < typename DstT, typename SrcT, typename = std::enable_if_t && std::is_pointer_v> > DstT alignment (SrcT src) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #ifdef COMPILER_GCC #pragma GCC diagnostic ignored "-Waddress-of-packed-member" #endif CHECK_MOD (reinterpret_cast (src), alignof (std::remove_pointer_t)); return reinterpret_cast (src); #pragma GCC diagnostic pop } /////////////////////////////////////////////////////////////////////////// /// Cast from SrcT to DstT and damn any consequences; just make it compile. template DstT ffs (SrcT src) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wold-style-cast" #if defined(COMPILER_GCC) #pragma GCC diagnostic ignored "-Wcast-function-type" #endif return (DstT)src; #pragma GCC diagnostic pop } /////////////////////////////////////////////////////////////////////////// /// Cast from SrcT to DstT, performing sanity checks on the src and dst /// values before returning the result. template DstT sanity (SrcT src) { cruft::debug::sanity (src); DstT dst = static_cast (src); cruft::debug::sanity (dst); return dst; } }