diff options
-rw-r--r-- | indra/llcommon/stdtypes.h | 106 |
1 files changed, 102 insertions, 4 deletions
diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h index b07805b628..a030017e3b 100644 --- a/indra/llcommon/stdtypes.h +++ b/indra/llcommon/stdtypes.h @@ -26,16 +26,23 @@ #ifndef LL_STDTYPES_H #define LL_STDTYPES_H +#include <cassert> #include <cfloat> #include <climits> +#include <limits> +#include <type_traits> -typedef signed char S8; +typedef signed char S8; typedef unsigned char U8; typedef signed short S16; typedef unsigned short U16; -typedef signed int S32; +typedef signed int S32; typedef unsigned int U32; +// to express an index that might go negative +// (ssize_t is provided by SOME compilers, don't collide) +typedef typename std::make_signed<size_t>::type llssize; + #if LL_WINDOWS // https://docs.microsoft.com/en-us/cpp/build/reference/zc-wchar-t-wchar-t-is-native-type // https://docs.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp @@ -45,7 +52,7 @@ typedef unsigned int U32; // The version of clang available with VS 2019 also defines wchar_t as __wchar_t // which is also 16 bits. // In any case, llwchar should be a UTF-32 type. -typedef U32 llwchar; +typedef U32 llwchar; #else typedef wchar_t llwchar; // What we'd actually want is a simple module-scope 'if constexpr' to test @@ -76,7 +83,7 @@ typedef double F64; typedef S32 BOOL; typedef U8 KEY; typedef U32 MASK; -typedef U32 TPACKETID; +typedef U32 TPACKETID; // Use #define instead of consts to avoid conversion headaches #define S8_MAX (SCHAR_MAX) @@ -118,4 +125,95 @@ typedef U8 LLPCode; typedef int intptr_t; #endif +/***************************************************************************** +* Narrowing +*****************************************************************************/ +/** + * narrow() is used to cast a wider type to a narrower type with validation. + * + * In many cases we take the size() of a container and try to pass it to an + * S32 or a U32 parameter. We used to be able to assume that the size of + * anything we could fit into memory could be expressed as a 32-bit int. With + * 64-bit viewers, though, size_t as returned by size() and length() and so + * forth is 64 bits, and the compiler is unhappy about stuffing such values + * into 32-bit types. + * + * It works to force the compiler to truncate, e.g. static_cast<S32>(len) or + * S32(len) or (S32)len, but we can do better. + * + * For: + * @code + * std::vector<Object> container; + * void somefunc(S32 size); + * @endcode + * call: + * @code + * somefunc(narrow(container.size())); + * @endcode + * + * narrow() truncates but, in RelWithDebInfo builds, it validates (using + * assert()) that the passed value can validly be expressed by the destination + * type. + */ +// narrow_holder is a struct that accepts the passed value as its original +// type and provides templated conversion functions to other types. Once we're +// building with compilers that support Class Template Argument Deduction, we +// can rename this class template 'narrow' and eliminate the narrow() factory +// function below. +template <typename FROM> +class narrow_holder +{ +private: + FROM mValue; + +public: + narrow_holder(FROM value): mValue(value) {} + + /*---------------------- Narrowing unsigned to signed ----------------------*/ + template <typename TO, + typename std::enable_if<std::is_unsigned<FROM>::value && + std::is_signed<TO>::value, + bool>::type = true> + inline + operator TO() const + { + // The reason we skip the + // assert(value >= std::numeric_limits<TO>::lowest()); + // in the overload below is that to perform the above comparison, the + // compiler promotes the signed lowest() to the unsigned FROM type, + // making it hugely positive -- so a reasonable 'value' will always + // fail the assert(). + assert(mValue <= std::numeric_limits<TO>::max()); + return static_cast<TO>(mValue); + } + + /*----------------------- Narrowing all other cases ------------------------*/ + template <typename TO, + typename std::enable_if<! (std::is_unsigned<FROM>::value && + std::is_signed<TO>::value), + bool>::type = true> + inline + operator TO() const + { + // two different assert()s so we can tell which condition failed + assert(mValue <= std::numeric_limits<TO>::max()); + // Funny, with floating point types min() is "positive epsilon" rather + // than "largest negative" -- that's lowest(). + assert(mValue >= std::numeric_limits<TO>::lowest()); + // Do we really expect to use this with floating point types? + // If so, does it matter if a very small value truncates to zero? + //assert(fabs(mValue) >= std::numeric_limits<TO>::min()); + return static_cast<TO>(mValue); + } +}; + +/// narrow() factory function returns a narrow_holder<FROM>(), which can be +/// implicitly converted to the target type. +template <typename FROM> +inline +narrow_holder<FROM> narrow(FROM value) +{ + return { value }; +} + #endif |