/* * Copyright 2013 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef SkTFitsIn_DEFINED #define SkTFitsIn_DEFINED #include "../private/SkTLogic.h" #include #include /** * In C++ an unsigned to signed cast where the source value cannot be represented in the destination * type results in an implementation defined destination value. Unlike C, C++ does not allow a trap. * This makes "(S)(D)s == s" a possibly useful test. However, there are two cases where this is * incorrect: * * when testing if a value of a smaller signed type can be represented in a larger unsigned type * (int8_t)(uint16_t)-1 == -1 => (int8_t)0xFFFF == -1 => [implementation defined] == -1 * * when testing if a value of a larger unsigned type can be represented in a smaller signed type * (uint16_t)(int8_t)0xFFFF == 0xFFFF => (uint16_t)-1 == 0xFFFF => 0xFFFF == 0xFFFF => true. * * Consider the cases: * u = unsigned, s = signed, X = more digits, x = less digits * ux -> uX: (ux)(uX)ux == ux, trivially true * uX -> ux: (uX)(ux)uX == uX, both casts well defined, test works * sx -> sX: (sx)(sX)sx == sx, trivially true * sX -> sx: (sX)(sx)sX == sX, first cast implementation value, second cast defined, test works * sx -> uX: (sx)(uX)sx == sx, this is bad, the second cast results in implementation defined value * sX -> ux: (sX)(ux)sX == sX, the second cast is required to prevent promotion of rhs to unsigned * ux -> sX: (ux)(sX)ux == ux, trivially true * uX -> sx: (uX)(sx)uX == uX, this is bad, * first cast results in implementation defined value, * second cast is defined. However, this creates false positives * uint16_t x = 0xFFFF * (uint16_t)(int8_t)x == x * => (uint16_t)-1 == x * => 0xFFFF == x * => true * * So for the eight cases three are trivially true, three more are valid casts, and two are special. * The two 'full' checks which otherwise require two comparisons are valid cast checks. * The two remaining checks uX -> sx [uX < max(sx)] and sx -> uX [sx > 0] can be done with one op. */ namespace sktfitsin { namespace Private { /** SkTMux::type = (a && b) ? Both : (a) ? A : (b) ? B : Neither; */ template struct SkTMux { using type = skstd::conditional_t, skstd::conditional_t>; }; /** SkTHasMoreDigits = (digits(A) >= digits(B)) ? true_type : false_type. */ template struct SkTHasMoreDigits : skstd::bool_constant::digits >= std::numeric_limits::digits> { }; /** Returns true. * Used when it is statically known that source values are in the range of the Destination. */ template struct SkTInRange_True { static constexpr bool fits(S) { return true; } }; /** Tests that (S)(D)s == s. * This is not valid for uX -> sx and sx -> uX conversions. */ template struct SkTInRange_Cast { static constexpr bool fits(S s) { using S_is_bigger = SkTHasMoreDigits; using D_is_bigger = SkTHasMoreDigits; using S_is_signed = skstd::bool_constant::is_signed>; using D_is_signed = skstd::bool_constant::is_signed>; using precondition = skstd::bool_constant< !((!S_is_signed::value && D_is_signed::value && S_is_bigger::value) || ( S_is_signed::value && !D_is_signed::value && D_is_bigger::value) )>; static_assert(precondition::value, "not valid for uX -> sx and sx -> uX conversions"); return static_cast(static_cast(s)) == s; } }; /** Tests if the source value <= Max(D). * Assumes that Max(S) >= Max(D). */ template struct SkTInRange_LE_MaxD { static constexpr bool fits(S s) { using precondition = SkTHasMoreDigits; static_assert(precondition::value, "maxS < maxD"); return s <= static_cast((std::numeric_limits::max)()); } }; /** Tests if the source value >= 0. */ template struct SkTInRange_GE_Zero { static constexpr bool fits(S s) { return static_cast(0) <= s; } }; /** SkTFitsIn_Unsigned2Unsiged::type is an SkTInRange with an fits(S s) method * the implementation of which is tailored for the source and destination types. * Assumes that S and D are unsigned integer types. */ template struct SkTFitsIn_Unsigned2Unsiged { using CastCheck = SkTInRange_Cast; using NoCheck = SkTInRange_True; // If std::numeric_limits::digits >= std::numeric_limits::digits, nothing to check. using sourceFitsInDesitination = SkTHasMoreDigits; using type = skstd::conditional_t; }; /** SkTFitsIn_Signed2Signed::type is an SkTInRange with an fits(S s) method * the implementation of which is tailored for the source and destination types. * Assumes that S and D are signed integer types. */ template struct SkTFitsIn_Signed2Signed { using CastCheck = SkTInRange_Cast; using NoCheck = SkTInRange_True; // If std::numeric_limits::digits >= std::numeric_limits::digits, nothing to check. using sourceFitsInDesitination = SkTHasMoreDigits; using type = skstd::conditional_t; }; /** SkTFitsIn_Signed2Unsigned::type is an SkTInRange with an fits(S s) method * the implementation of which is tailored for the source and destination types. * Assumes that S is a signed integer type and D is an unsigned integer type. */ template struct SkTFitsIn_Signed2Unsigned { using CastCheck = SkTInRange_Cast; using LowSideOnlyCheck = SkTInRange_GE_Zero; // If std::numeric_limits::max() >= std::numeric_limits::max(), // no need to check the high side. (Until C++11, assume more digits means greater max.) // This also protects the precondition of SkTInRange_Cast. using sourceCannotExceedDest = SkTHasMoreDigits; using type = skstd::conditional_t; }; /** SkTFitsIn_Unsigned2Signed::type is an SkTInRange with an fits(S s) method * the implementation of which is tailored for the source and destination types. * Assumes that S is an usigned integer type and D is a signed integer type. */ template struct SkTFitsIn_Unsigned2Signed { using HighSideCheck = SkTInRange_LE_MaxD; using NoCheck = SkTInRange_True; // If std::numeric_limits::max() >= std::numeric_limits::max(), nothing to check. // (Until C++11, assume more digits means greater max.) using sourceCannotExceedDest = SkTHasMoreDigits; using type = skstd::conditional_t; }; /** SkTFitsIn::type is an SkTInRange with an fits(S s) method * the implementation of which is tailored for the source and destination types. * Assumes that S and D are integer types. */ template struct SkTFitsIn { // One of the following will be the 'selector' type. using S2S = SkTFitsIn_Signed2Signed; using S2U = SkTFitsIn_Signed2Unsigned; using U2S = SkTFitsIn_Unsigned2Signed; using U2U = SkTFitsIn_Unsigned2Unsiged; using S_is_signed = skstd::bool_constant::is_signed>; using D_is_signed = skstd::bool_constant::is_signed>; using selector = typename SkTMux::type; // This type is an SkTInRange. using type = typename selector::type; }; template ::value> struct underlying_type { using type = skstd::underlying_type_t; }; template struct underlying_type { using type = T; }; } // namespace Private } // namespace sktfitsin /** Returns true if the integer source value 's' will fit in the integer destination type 'D'. */ template constexpr inline bool SkTFitsIn(S s) { static_assert(std::is_integral::value || std::is_enum::value, "S must be integral."); static_assert(std::is_integral::value || std::is_enum::value, "D must be integral."); using RealS = typename sktfitsin::Private::underlying_type::type; using RealD = typename sktfitsin::Private::underlying_type::type; return sktfitsin::Private::SkTFitsIn::type::fits(s); } #endif