/* * 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 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. * * Copyright 2011 Danny Robson */ #ifndef __UTIL_CAST_HPP #define __UTIL_CAST_HPP #include "debug.hpp" #include #include namespace util::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. // // checks will be compiled out if NDEBUG is defined. template < typename NarrowT, typename WideT, typename = std::enable_if_t< std::is_arithmetic_v && std::is_arithmetic_v && std::is_signed_v == std::is_signed_v && std::is_floating_point_v == std::is_floating_point_v && sizeof (NarrowT) <= sizeof (WideT), void > > 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 equality holds with the result // // checks will be compiled out if NDEBUG is defined. template constexpr DstT lossless (const SrcT &src) { #ifndef NDEBUG 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); return dst; #else return static_cast (src); #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 T* known (V *v) { CHECK (dynamic_cast (v)); return static_cast (v); } //------------------------------------------------------------------------- template T& known (V &v) { CHECK_NOTHROW (dynamic_cast (v)); return static_cast (v); } /////////////////////////////////////////////////////////////////////////// /// cast a pointer from one type to another, asserting that the required /// alignment of the destination type has been satisfied. 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" CHECK_MOD (reinterpret_cast (src), alignof (DstT)); return reinterpret_cast (src); #pragma GCC diagnostic pop } }; #endif