Update core/fxcrt/numerics
Synchronize with Chromium base/numerics at https://crrev.com/1444872.
Then let `git cl format` do its thing.
Change-Id: Iad62904aaa0d0f05500da8f2473b46c3be9225a7
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130990
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
index 21060a4..37ad003 100644
--- a/core/fxcrt/BUILD.gn
+++ b/core/fxcrt/BUILD.gn
@@ -93,6 +93,7 @@
"numerics/checked_math_impl.h",
"numerics/clamped_math.h",
"numerics/clamped_math_impl.h",
+ "numerics/integral_constant_like.h",
"numerics/safe_conversions.h",
"numerics/safe_conversions_arm_impl.h",
"numerics/safe_conversions_impl.h",
diff --git a/core/fxcrt/numerics/checked_math.h b/core/fxcrt/numerics/checked_math.h
index 6c5973b..09c1c39 100644
--- a/core/fxcrt/numerics/checked_math.h
+++ b/core/fxcrt/numerics/checked_math.h
@@ -5,25 +5,22 @@
#ifndef CORE_FXCRT_NUMERICS_CHECKED_MATH_H_
#define CORE_FXCRT_NUMERICS_CHECKED_MATH_H_
-#include <stddef.h>
+#include <stdint.h>
#include <limits>
#include <type_traits>
-#include "core/fxcrt/numerics/checked_math_impl.h"
+#include "core/fxcrt/numerics/checked_math_impl.h" // IWYU pragma: export
+#include "core/fxcrt/numerics/safe_conversions.h"
+#include "core/fxcrt/numerics/safe_math_shared_impl.h" // IWYU pragma: export
namespace pdfium {
namespace internal {
template <typename T>
+ requires std::is_arithmetic_v<T>
class CheckedNumeric {
- static_assert(std::is_arithmetic<T>::value,
- "CheckedNumeric<T>: T must be a numeric type.");
-
public:
- template <typename Src>
- friend class CheckedNumeric;
-
using type = T;
constexpr CheckedNumeric() = default;
@@ -33,16 +30,10 @@
constexpr CheckedNumeric(const CheckedNumeric<Src>& rhs)
: state_(rhs.state_.value(), rhs.IsValid()) {}
- // Strictly speaking, this is not necessary, but declaring this allows class
- // template argument deduction to be used so that it is possible to simply
- // write `CheckedNumeric(777)` instead of `CheckedNumeric<int>(777)`.
- // NOLINTNEXTLINE(google-explicit-constructor)
- constexpr CheckedNumeric(T value) : state_(value) {}
-
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to CheckedNumerics to make them easier to use.
- template <typename Src,
- typename = std::enable_if_t<std::is_arithmetic<Src>::value>>
+ template <typename Src>
+ requires(std::is_arithmetic_v<Src>)
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr CheckedNumeric(Src value) : state_(value) {}
@@ -73,9 +64,11 @@
#endif
constexpr bool
AssignIfValid(Dst* result) const {
- return BASE_NUMERICS_LIKELY(IsValid<Dst>())
- ? ((*result = static_cast<Dst>(state_.value())), true)
- : false;
+ if (IsValid<Dst>()) [[likely]] {
+ *result = static_cast<Dst>(state_.value());
+ return true;
+ }
+ return false;
}
// ValueOrDie() - The primary accessor for the underlying value. If the
@@ -88,9 +81,10 @@
// the underlying value, and it is not available through other means.
template <typename Dst = T, class CheckHandler = CheckOnFailure>
constexpr StrictNumeric<Dst> ValueOrDie() const {
- return BASE_NUMERICS_LIKELY(IsValid<Dst>())
- ? static_cast<Dst>(state_.value())
- : CheckHandler::template HandleFailure<Dst>();
+ if (IsValid<Dst>()) [[likely]] {
+ return static_cast<Dst>(state_.value());
+ }
+ return CheckHandler::template HandleFailure<Dst>();
}
// ValueOrDefault(T default_value) - A convenience method that returns the
@@ -100,17 +94,18 @@
// parameter. WARNING: This function may fail to compile or CHECK at runtime
// if the supplied default_value is not within range of the destination type.
template <typename Dst = T, typename Src>
- constexpr StrictNumeric<Dst> ValueOrDefault(const Src default_value) const {
- return BASE_NUMERICS_LIKELY(IsValid<Dst>())
- ? static_cast<Dst>(state_.value())
- : checked_cast<Dst>(default_value);
+ constexpr StrictNumeric<Dst> ValueOrDefault(Src default_value) const {
+ if (IsValid<Dst>()) [[likely]] {
+ return static_cast<Dst>(state_.value());
+ }
+ return checked_cast<Dst>(default_value);
}
// Returns a checked numeric of the specified type, cast from the current
// CheckedNumeric. If the current state is invalid or the destination cannot
// represent the result then the returned CheckedNumeric will be invalid.
template <typename Dst>
- constexpr CheckedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+ constexpr CheckedNumeric<UnderlyingType<Dst>> Cast() const {
return *this;
}
@@ -144,14 +139,14 @@
constexpr CheckedNumeric operator-() const {
// Use an optimized code path for a known run-time variable.
- if (!IsConstantEvaluated() && std::is_signed<T>::value &&
- std::is_floating_point<T>::value) {
+ if (!std::is_constant_evaluated() && std::is_signed_v<T> &&
+ std::is_floating_point_v<T>) {
return FastRuntimeNegate();
}
// The negation of two's complement int min is int min.
const bool is_valid =
IsValid() &&
- (!std::is_signed<T>::value || std::is_floating_point<T>::value ||
+ (!std::is_signed_v<T> || std::is_floating_point_v<T> ||
NegateWrapper(state_.value()) != std::numeric_limits<T>::lowest());
return CheckedNumeric<T>(NegateWrapper(state_.value()), is_valid);
}
@@ -167,13 +162,13 @@
template <typename U>
constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
- const U rhs) const {
+ U rhs) const {
return CheckMax(*this, rhs);
}
template <typename U>
constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
- const U rhs) const {
+ U rhs) const {
return CheckMin(*this, rhs);
}
@@ -192,8 +187,8 @@
}
constexpr CheckedNumeric operator++(int) {
- CheckedNumeric value = *this;
- *this += 1;
+ const CheckedNumeric value = *this;
+ ++*this;
return value;
}
@@ -203,18 +198,15 @@
}
constexpr CheckedNumeric operator--(int) {
- // TODO(pkasting): Consider std::exchange() once it's constexpr in C++20.
const CheckedNumeric value = *this;
- *this -= 1;
+ --*this;
return value;
}
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
- template <template <typename, typename, typename> class M,
- typename L,
- typename R>
- static constexpr CheckedNumeric MathOp(const L lhs, const R rhs) {
+ template <template <typename, typename> class M, typename L, typename R>
+ static constexpr CheckedNumeric MathOp(L lhs, R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
T result = 0;
const bool is_valid =
@@ -224,8 +216,8 @@
}
// Assignment arithmetic operations.
- template <template <typename, typename, typename> class M, typename R>
- constexpr CheckedNumeric& MathOp(const R rhs) {
+ template <template <typename, typename> class M, typename R>
+ constexpr CheckedNumeric& MathOp(R rhs) {
using Math = typename MathWrapper<M, T, R>::math;
T result = 0; // Using T as the destination saves a range check.
const bool is_valid =
@@ -236,6 +228,10 @@
}
private:
+ template <typename U>
+ requires std::is_arithmetic_v<U>
+ friend class CheckedNumeric;
+
CheckedNumericState<T> state_;
CheckedNumeric FastRuntimeNegate() const {
@@ -258,23 +254,26 @@
template <typename Src>
struct Wrapper<CheckedNumeric<Src>> {
- static constexpr bool is_valid(const CheckedNumeric<Src> v) {
+ static constexpr bool is_valid(CheckedNumeric<Src> v) {
return v.IsValid();
}
- static constexpr Src value(const CheckedNumeric<Src> v) {
+ static constexpr Src value(CheckedNumeric<Src> v) {
return v.state_.value();
}
};
template <typename Src>
struct Wrapper<StrictNumeric<Src>> {
- static constexpr bool is_valid(const StrictNumeric<Src>) { return true; }
- static constexpr Src value(const StrictNumeric<Src> v) {
+ static constexpr bool is_valid(StrictNumeric<Src>) { return true; }
+ static constexpr Src value(StrictNumeric<Src> v) {
return static_cast<Src>(v);
}
};
};
+template <typename T>
+CheckedNumeric(T) -> CheckedNumeric<T>;
+
// Convenience functions to avoid the ugly template disambiguator syntax.
template <typename Dst, typename Src>
constexpr bool IsValidForType(const CheckedNumeric<Src> value) {
@@ -288,38 +287,34 @@
}
template <typename Dst, typename Src, typename Default>
-constexpr StrictNumeric<Dst> ValueOrDefaultForType(
- const CheckedNumeric<Src> value,
- const Default default_value) {
+constexpr StrictNumeric<Dst> ValueOrDefaultForType(CheckedNumeric<Src> value,
+ Default default_value) {
return value.template ValueOrDefault<Dst>(default_value);
}
// Convenience wrapper to return a new CheckedNumeric from the provided
// arithmetic or CheckedNumericType.
template <typename T>
-constexpr CheckedNumeric<typename UnderlyingType<T>::type> MakeCheckedNum(
- const T value) {
+constexpr CheckedNumeric<UnderlyingType<T>> MakeCheckedNum(T value) {
return value;
}
// These implement the variadic wrapper for the math operations.
-template <template <typename, typename, typename> class M,
- typename L,
- typename R>
+template <template <typename, typename> class M, typename L, typename R>
constexpr CheckedNumeric<typename MathWrapper<M, L, R>::type> CheckMathOp(
- const L lhs,
- const R rhs) {
+ L lhs,
+ R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return CheckedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
rhs);
}
// General purpose wrapper template for arithmetic operations.
-template <template <typename, typename, typename> class M,
+template <template <typename, typename> class M,
typename L,
typename R,
typename... Args>
-constexpr auto CheckMathOp(const L lhs, const R rhs, const Args... args) {
+constexpr auto CheckMathOp(L lhs, R rhs, Args... args) {
return CheckMathOp<M>(CheckMathOp<M>(lhs, rhs), args...);
}
@@ -340,7 +335,7 @@
// arithmetic with our result types. Since wrapping on a pointer is always
// bad, we trigger the CHECK condition here.
template <typename L, typename R>
-L* operator+(L* lhs, const StrictNumeric<R> rhs) {
+L* operator+(L* lhs, StrictNumeric<R> rhs) {
const uintptr_t result = CheckAdd(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>();
@@ -348,7 +343,7 @@
}
template <typename L, typename R>
-L* operator-(L* lhs, const StrictNumeric<R> rhs) {
+L* operator-(L* lhs, StrictNumeric<R> rhs) {
const uintptr_t result = CheckSub(reinterpret_cast<uintptr_t>(lhs),
CheckMul(sizeof(L), static_cast<R>(rhs)))
.template ValueOrDie<uintptr_t>();
diff --git a/core/fxcrt/numerics/checked_math_impl.h b/core/fxcrt/numerics/checked_math_impl.h
index 7ee488d..0e2071b 100644
--- a/core/fxcrt/numerics/checked_math_impl.h
+++ b/core/fxcrt/numerics/checked_math_impl.h
@@ -5,24 +5,24 @@
#ifndef CORE_FXCRT_NUMERICS_CHECKED_MATH_IMPL_H_
#define CORE_FXCRT_NUMERICS_CHECKED_MATH_IMPL_H_
-#include <stddef.h>
+// IWYU pragma: private, include "core/fxcrt/numerics/checked_math.h"
+
#include <stdint.h>
-#include <climits>
#include <cmath>
-#include <cstdlib>
+#include <concepts>
#include <limits>
#include <type_traits>
#include "core/fxcrt/numerics/safe_conversions.h"
-#include "core/fxcrt/numerics/safe_math_shared_impl.h"
+#include "core/fxcrt/numerics/safe_math_shared_impl.h" // IWYU pragma: export
namespace pdfium {
namespace internal {
template <typename T>
constexpr bool CheckedAddImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
+ static_assert(std::integral<T>, "Type must be integral");
// Since the value of x+y is undefined if we have a signed type, we compute
// it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
@@ -32,7 +32,7 @@
const UnsignedDst uresult = static_cast<UnsignedDst>(ux + uy);
// Addition is valid if the sign of (x + y) is equal to either that of x or
// that of y.
- if (std::is_signed<T>::value
+ if (std::is_signed_v<T>
? static_cast<SignedDst>((uresult ^ ux) & (uresult ^ uy)) < 0
: uresult < uy) { // Unsigned is either valid or underflow.
return false;
@@ -41,15 +41,13 @@
return true;
}
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedAddOp {};
template <typename T, typename U>
-struct CheckedAddOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedAddOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
if constexpr (CheckedAddFastOp<T, U>::is_supported) {
@@ -57,22 +55,21 @@
}
// Double the underlying type up to a full machine word.
- using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ using FastPromotion = FastIntegerArithmeticPromotion<T, U>;
using Promotion =
- typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
- IntegerBitsPlusSign<intptr_t>::value),
- typename BigEnoughPromotion<T, U>::type,
- FastPromotion>::type;
+ std::conditional_t<(kIntegerBitsPlusSign<FastPromotion> >
+ kIntegerBitsPlusSign<intptr_t>),
+ BigEnoughPromotion<T, U>, FastPromotion>;
// Fail if either operand is out of range for the promoted type.
// TODO(jschuh): This could be made to work for a broader range of values.
- if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
- !IsValueInRangeForNumericType<Promotion>(y))) {
+ if (!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y)) [[unlikely]] {
return false;
}
Promotion presult = {};
bool is_valid = true;
- if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+ if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
presult = static_cast<Promotion>(x) + static_cast<Promotion>(y);
} else {
is_valid = CheckedAddImpl(static_cast<Promotion>(x),
@@ -88,7 +85,7 @@
template <typename T>
constexpr bool CheckedSubImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
+ static_assert(std::integral<T>, "Type must be integral");
// Since the value of x+y is undefined if we have a signed type, we compute
// it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
@@ -98,7 +95,7 @@
const UnsignedDst uresult = static_cast<UnsignedDst>(ux - uy);
// Subtraction is valid if either x and y have same sign, or (x-y) and x have
// the same sign.
- if (std::is_signed<T>::value
+ if (std::is_signed_v<T>
? static_cast<SignedDst>((uresult ^ ux) & (ux ^ uy)) < 0
: x < y) {
return false;
@@ -107,15 +104,13 @@
return true;
}
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedSubOp {};
template <typename T, typename U>
-struct CheckedSubOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedSubOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
if constexpr (CheckedSubFastOp<T, U>::is_supported) {
@@ -123,22 +118,21 @@
}
// Double the underlying type up to a full machine word.
- using FastPromotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ using FastPromotion = FastIntegerArithmeticPromotion<T, U>;
using Promotion =
- typename std::conditional<(IntegerBitsPlusSign<FastPromotion>::value >
- IntegerBitsPlusSign<intptr_t>::value),
- typename BigEnoughPromotion<T, U>::type,
- FastPromotion>::type;
+ std::conditional_t<(kIntegerBitsPlusSign<FastPromotion> >
+ kIntegerBitsPlusSign<intptr_t>),
+ BigEnoughPromotion<T, U>, FastPromotion>;
// Fail if either operand is out of range for the promoted type.
// TODO(jschuh): This could be made to work for a broader range of values.
- if (BASE_NUMERICS_UNLIKELY(!IsValueInRangeForNumericType<Promotion>(x) ||
- !IsValueInRangeForNumericType<Promotion>(y))) {
+ if (!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y)) [[unlikely]] {
return false;
}
Promotion presult = {};
bool is_valid = true;
- if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+ if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
presult = static_cast<Promotion>(x) - static_cast<Promotion>(y);
} else {
is_valid = CheckedSubImpl(static_cast<Promotion>(x),
@@ -154,7 +148,7 @@
template <typename T>
constexpr bool CheckedMulImpl(T x, T y, T* result) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
+ static_assert(std::integral<T>, "Type must be integral");
// Since the value of x*y is potentially undefined if we have a signed type,
// we compute it using the unsigned type of the same size.
using UnsignedDst = typename std::make_unsigned<T>::type;
@@ -163,11 +157,11 @@
const UnsignedDst uy = SafeUnsignedAbs(y);
const UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
const bool is_negative =
- std::is_signed<T>::value && static_cast<SignedDst>(x ^ y) < 0;
+ std::is_signed_v<T> && static_cast<SignedDst>(x ^ y) < 0;
// We have a fast out for unsigned identity or zero on the second operand.
// After that it's an unsigned overflow check on the absolute value, with
// a +1 bound for a negative result.
- if (uy > UnsignedDst(!std::is_signed<T>::value || is_negative) &&
+ if (uy > UnsignedDst(!std::is_signed_v<T> || is_negative) &&
ux > (std::numeric_limits<T>::max() + UnsignedDst(is_negative)) / uy) {
return false;
}
@@ -175,38 +169,36 @@
return true;
}
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedMulOp {};
template <typename T, typename U>
-struct CheckedMulOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedMulOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
if constexpr (CheckedMulFastOp<T, U>::is_supported) {
return CheckedMulFastOp<T, U>::Do(x, y, result);
}
- using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ using Promotion = FastIntegerArithmeticPromotion<T, U>;
// Verify the destination type can hold the result (always true for 0).
- if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
- !IsValueInRangeForNumericType<Promotion>(y)) &&
- x && y)) {
+ if ((!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y)) &&
+ x && y) [[unlikely]] {
return false;
}
Promotion presult = {};
bool is_valid = true;
- if (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
+ if constexpr (CheckedMulFastOp<Promotion, Promotion>::is_supported) {
// The fast op may be available with the promoted type.
// The casts here are safe because of the "value in range" conditional
// above.
is_valid = CheckedMulFastOp<Promotion, Promotion>::Do(
static_cast<Promotion>(x), static_cast<Promotion>(y), &presult);
- } else if (IsIntegerArithmeticSafe<Promotion, T, U>::value) {
+ } else if constexpr (kIsIntegerArithmeticSafe<Promotion, T, U>) {
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
} else {
is_valid = CheckedMulImpl(static_cast<Promotion>(x),
@@ -222,37 +214,33 @@
// Division just requires a check for a zero denominator or an invalid negation
// on signed min/-1.
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedDivOp {};
template <typename T, typename U>
-struct CheckedDivOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedDivOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
- if (BASE_NUMERICS_UNLIKELY(!y)) {
+ if (!y) [[unlikely]] {
return false;
}
// The overflow check can be compiled away if we don't have the exact
// combination of types needed to trigger this case.
- using Promotion = typename BigEnoughPromotion<T, U>::type;
- if (BASE_NUMERICS_UNLIKELY(
- (std::is_signed<T>::value && std::is_signed<U>::value &&
- IsTypeInRangeForNumericType<T, Promotion>::value &&
- static_cast<Promotion>(x) ==
- std::numeric_limits<Promotion>::lowest() &&
- y == static_cast<U>(-1)))) {
+ using Promotion = BigEnoughPromotion<T, U>;
+ if (std::is_signed_v<T> && std::is_signed_v<U> &&
+ kIsTypeInRangeForNumericType<T, Promotion> &&
+ static_cast<Promotion>(x) == std::numeric_limits<Promotion>::lowest() &&
+ y == static_cast<U>(-1)) [[unlikely]] {
return false;
}
// This branch always compiles away if the above branch wasn't removed.
- if (BASE_NUMERICS_UNLIKELY((!IsValueInRangeForNumericType<Promotion>(x) ||
- !IsValueInRangeForNumericType<Promotion>(y)) &&
- x)) {
+ if ((!IsValueInRangeForNumericType<Promotion>(x) ||
+ !IsValueInRangeForNumericType<Promotion>(y)) &&
+ x) [[unlikely]] {
return false;
}
@@ -265,28 +253,24 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedModOp {};
template <typename T, typename U>
-struct CheckedModOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedModOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
- if (BASE_NUMERICS_UNLIKELY(!y)) {
+ if (!y) [[unlikely]] {
return false;
}
- using Promotion = typename BigEnoughPromotion<T, U>::type;
- if (BASE_NUMERICS_UNLIKELY(
- (std::is_signed<T>::value && std::is_signed<U>::value &&
- IsTypeInRangeForNumericType<T, Promotion>::value &&
- static_cast<Promotion>(x) ==
- std::numeric_limits<Promotion>::lowest() &&
- y == static_cast<U>(-1)))) {
+ using Promotion = BigEnoughPromotion<T, U>;
+ if (std::is_signed_v<T> && std::is_signed_v<U> &&
+ kIsTypeInRangeForNumericType<T, Promotion> &&
+ static_cast<Promotion>(x) == std::numeric_limits<Promotion>::lowest() &&
+ y == static_cast<U>(-1)) [[unlikely]] {
*result = 0;
return true;
}
@@ -301,24 +285,22 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedLshOp {};
// Left shift. Shifts less than 0 or greater than or equal to the number
// of bits in the promoted type are undefined. Shifts of negative values
// are undefined. Otherwise it is defined when the result fits.
template <typename T, typename U>
-struct CheckedLshOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedLshOp<T, U> {
using result_type = T;
template <typename V>
static constexpr bool Do(T x, U shift, V* result) {
// Disallow negative numbers and verify the shift is in bounds.
- if (BASE_NUMERICS_LIKELY(!IsValueNegative(x) &&
- as_unsigned(shift) <
- as_unsigned(std::numeric_limits<T>::digits))) {
+ if (!IsValueNegative(x) &&
+ as_unsigned(shift) < as_unsigned(std::numeric_limits<T>::digits))
+ [[likely]] {
// Shift as unsigned to avoid undefined behavior.
*result = static_cast<V>(as_unsigned(x) << shift);
// If the shift can be reversed, we know it was valid.
@@ -326,7 +308,7 @@
}
// Handle the legal corner-case of a full-width signed shift of zero.
- if (!std::is_signed<T>::value || x ||
+ if (!std::is_signed_v<T> || x ||
as_unsigned(shift) != as_unsigned(std::numeric_limits<T>::digits)) {
return false;
}
@@ -335,23 +317,20 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedRshOp {};
// Right shift. Shifts less than 0 or greater than or equal to the number
// of bits in the promoted type are undefined. Otherwise, it is always defined,
// but a right shift of a negative value is implementation-dependent.
template <typename T, typename U>
-struct CheckedRshOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedRshOp<T, U> {
using result_type = T;
template <typename V>
static constexpr bool Do(T x, U shift, V* result) {
// Use sign conversion to push negative values out of range.
- if (BASE_NUMERICS_UNLIKELY(as_unsigned(shift) >=
- IntegerBitsPlusSign<T>::value)) {
+ if (as_unsigned(shift) >= kIntegerBitsPlusSign<T>) [[unlikely]] {
return false;
}
@@ -364,17 +343,14 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedAndOp {};
// For simplicity we support only unsigned integer results.
template <typename T, typename U>
-struct CheckedAndOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedAndOp<T, U> {
+ using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
const result_type tmp =
@@ -387,17 +363,14 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedOrOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
-struct CheckedOrOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedOrOp<T, U> {
+ using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
const result_type tmp =
@@ -410,17 +383,14 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedXorOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
-struct CheckedXorOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct CheckedXorOp<T, U> {
+ using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
const result_type tmp =
@@ -435,16 +405,13 @@
// Max doesn't really need to be implemented this way because it can't fail,
// but it makes the code much cleaner to use the MathOp wrappers.
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedMaxOp {};
template <typename T, typename U>
-struct CheckedMaxOp<
- T,
- U,
- typename std::enable_if<std::is_arithmetic<T>::value &&
- std::is_arithmetic<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct CheckedMaxOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
const result_type tmp = IsGreater<T, U>::Test(x, y)
@@ -460,16 +427,13 @@
// Min doesn't really need to be implemented this way because it can't fail,
// but it makes the code much cleaner to use the MathOp wrappers.
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct CheckedMinOp {};
template <typename T, typename U>
-struct CheckedMinOp<
- T,
- U,
- typename std::enable_if<std::is_arithmetic<T>::value &&
- std::is_arithmetic<U>::value>::type> {
- using result_type = typename LowestValuePromotion<T, U>::type;
+ requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct CheckedMinOp<T, U> {
+ using result_type = LowestValuePromotion<T, U>;
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
const result_type tmp = IsLess<T, U>::Test(x, y)
@@ -485,22 +449,19 @@
// This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
-#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
- template <typename T, typename U> \
- struct Checked##NAME##Op< \
- T, U, \
- typename std::enable_if<std::is_floating_point<T>::value || \
- std::is_floating_point<U>::value>::type> { \
- using result_type = typename MaxExponentPromotion<T, U>::type; \
- template <typename V> \
- static constexpr bool Do(T x, U y, V* result) { \
- using Promotion = typename MaxExponentPromotion<T, U>::type; \
- const Promotion presult = x OP y; \
- if (!IsValueInRangeForNumericType<V>(presult)) \
- return false; \
- *result = static_cast<V>(presult); \
- return true; \
- } \
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
+ template <typename T, typename U> \
+ requires(std::floating_point<T> || std::floating_point<U>) \
+ struct Checked##NAME##Op<T, U> { \
+ using result_type = MaxExponentPromotion<T, U>; \
+ template <typename V> \
+ static constexpr bool Do(T x, U y, V* result) { \
+ const result_type presult = x OP y; \
+ if (!IsValueInRangeForNumericType<V>(presult)) \
+ return false; \
+ *result = static_cast<V>(presult); \
+ return true; \
+ } \
};
BASE_FLOAT_ARITHMETIC_OPS(Add, +)
@@ -522,10 +483,10 @@
template <typename NumericType>
struct GetNumericRepresentation {
static const NumericRepresentation value =
- std::is_integral<NumericType>::value
+ std::integral<NumericType>
? NUMERIC_INTEGER
- : (std::is_floating_point<NumericType>::value ? NUMERIC_FLOATING
- : NUMERIC_UNKNOWN);
+ : (std::floating_point<NumericType> ? NUMERIC_FLOATING
+ : NUMERIC_UNKNOWN);
};
template <typename T,
@@ -540,7 +501,7 @@
constexpr explicit CheckedNumericState(Src value = 0, bool is_valid = true)
: is_valid_(is_valid && IsValueInRangeForNumericType<T>(value)),
value_(WellDefinedConversionOrZero(value, is_valid_)) {
- static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
+ static_assert(std::is_arithmetic_v<Src>, "Argument must be numeric.");
}
template <typename Src>
@@ -555,8 +516,7 @@
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrZero(Src value, bool is_valid) {
- using SrcType = typename internal::UnderlyingType<Src>::type;
- return (std::is_integral<SrcType>::value || is_valid)
+ return (std::integral<UnderlyingType<Src>> || is_valid)
? static_cast<T>(value)
: 0;
}
@@ -583,8 +543,9 @@
: CheckedNumericState(rhs.value(), rhs.is_valid()) {}
constexpr bool is_valid() const {
- // Written this way because std::isfinite is not reliably constexpr.
- return IsConstantEvaluated()
+ // Written this way because std::isfinite is not constexpr before C++23.
+ // TODO(C++23): Use `std::isfinite()` unconditionally.
+ return std::is_constant_evaluated()
? value_ <= std::numeric_limits<T>::max() &&
value_ >= std::numeric_limits<T>::lowest()
: std::isfinite(value_);
@@ -596,9 +557,8 @@
// Ensures that a type conversion does not trigger undefined behavior.
template <typename Src>
static constexpr T WellDefinedConversionOrNaN(Src value, bool is_valid) {
- using SrcType = typename internal::UnderlyingType<Src>::type;
- return (StaticDstRangeRelationToSrcRange<T, SrcType>::value ==
- NUMERIC_RANGE_CONTAINED ||
+ return (kStaticDstRangeRelationToSrcRange<T, UnderlyingType<Src>> ==
+ NumericRangeRepresentation::kContained ||
is_valid)
? static_cast<T>(value)
: std::numeric_limits<T>::quiet_NaN();
diff --git a/core/fxcrt/numerics/clamped_math.h b/core/fxcrt/numerics/clamped_math.h
index 4e98785..597a76c 100644
--- a/core/fxcrt/numerics/clamped_math.h
+++ b/core/fxcrt/numerics/clamped_math.h
@@ -5,47 +5,34 @@
#ifndef CORE_FXCRT_NUMERICS_CLAMPED_MATH_H_
#define CORE_FXCRT_NUMERICS_CLAMPED_MATH_H_
-#include <stddef.h>
-
-#include <limits>
#include <type_traits>
-#include "core/fxcrt/numerics/clamped_math_impl.h"
+#include "core/fxcrt/numerics/clamped_math_impl.h" // IWYU pragma: export
+#include "core/fxcrt/numerics/safe_conversions.h"
+#include "core/fxcrt/numerics/safe_math_shared_impl.h" // IWYU pragma: export
namespace pdfium {
namespace internal {
template <typename T>
+ requires std::is_arithmetic_v<T>
class ClampedNumeric {
- static_assert(std::is_arithmetic<T>::value,
- "ClampedNumeric<T>: T must be a numeric type.");
-
public:
using type = T;
- constexpr ClampedNumeric() : value_(0) {}
+ constexpr ClampedNumeric() = default;
// Copy constructor.
template <typename Src>
constexpr ClampedNumeric(const ClampedNumeric<Src>& rhs)
: value_(saturated_cast<T>(rhs.value_)) {}
- template <typename Src>
- friend class ClampedNumeric;
-
- // Strictly speaking, this is not necessary, but declaring this allows class
- // template argument deduction to be used so that it is possible to simply
- // write `ClampedNumeric(777)` instead of `ClampedNumeric<int>(777)`.
- // NOLINTNEXTLINE(google-explicit-constructor)
- constexpr ClampedNumeric(T value) : value_(value) {}
-
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to ClampedNumerics to make them easier to use.
template <typename Src>
+ requires(IsNumeric<Src>)
// NOLINTNEXTLINE(google-explicit-constructor)
- constexpr ClampedNumeric(Src value) : value_(saturated_cast<T>(value)) {
- static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
- }
+ constexpr ClampedNumeric(Src value) : value_(saturated_cast<T>(value)) {}
// This is not an explicit constructor because we want a seamless conversion
// from StrictNumeric types.
@@ -57,7 +44,7 @@
// Returns a ClampedNumeric of the specified type, cast from the current
// ClampedNumeric, and saturated to the destination type.
template <typename Dst>
- constexpr ClampedNumeric<typename UnderlyingType<Dst>::type> Cast() const {
+ constexpr ClampedNumeric<UnderlyingType<Dst>> Cast() const {
return *this;
}
@@ -101,7 +88,7 @@
template <typename U>
constexpr ClampedNumeric<typename MathWrapper<ClampedMaxOp, T, U>::type> Max(
- const U rhs) const {
+ U rhs) const {
using result_type = typename MathWrapper<ClampedMaxOp, T, U>::type;
return ClampedNumeric<result_type>(
ClampedMaxOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
@@ -109,7 +96,7 @@
template <typename U>
constexpr ClampedNumeric<typename MathWrapper<ClampedMinOp, T, U>::type> Min(
- const U rhs) const {
+ U rhs) const {
using result_type = typename MathWrapper<ClampedMinOp, T, U>::type;
return ClampedNumeric<result_type>(
ClampedMinOp<T, U>::Do(value_, Wrapper<U>::value(rhs)));
@@ -148,18 +135,16 @@
// These perform the actual math operations on the ClampedNumerics.
// Binary arithmetic operations.
- template <template <typename, typename, typename> class M,
- typename L,
- typename R>
- static constexpr ClampedNumeric MathOp(const L lhs, const R rhs) {
+ template <template <typename, typename> class M, typename L, typename R>
+ static constexpr ClampedNumeric MathOp(L lhs, R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return ClampedNumeric<T>(
Math::template Do<T>(Wrapper<L>::value(lhs), Wrapper<R>::value(rhs)));
}
// Assignment arithmetic operations.
- template <template <typename, typename, typename> class M, typename R>
- constexpr ClampedNumeric& MathOp(const R rhs) {
+ template <template <typename, typename> class M, typename R>
+ constexpr ClampedNumeric& MathOp(R rhs) {
using Math = typename MathWrapper<M, T, R>::math;
*this =
ClampedNumeric<T>(Math::template Do<T>(value_, Wrapper<R>::value(rhs)));
@@ -167,9 +152,9 @@
}
template <typename Dst>
- constexpr operator Dst() const {
- return saturated_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(
- value_);
+ requires std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Dst>>
+ constexpr operator Dst() const { // NOLINT(google-explicit-constructor)
+ return saturated_cast<ArithmeticOrUnderlyingEnum<Dst>>(value_);
}
// This method extracts the raw integer value without saturating it to the
@@ -178,44 +163,46 @@
constexpr T RawValue() const { return value_; }
private:
- T value_;
+ template <typename U>
+ requires std::is_arithmetic_v<U>
+ friend class ClampedNumeric;
+
+ T value_ = 0;
// These wrappers allow us to handle state the same way for both
// ClampedNumeric and POD arithmetic types.
template <typename Src>
struct Wrapper {
- static constexpr typename UnderlyingType<Src>::type value(Src value) {
- return value;
- }
+ static constexpr UnderlyingType<Src> value(Src value) { return value; }
};
};
+template <typename T>
+ClampedNumeric(T) -> ClampedNumeric<T>;
+
// Convenience wrapper to return a new ClampedNumeric from the provided
// arithmetic or ClampedNumericType.
template <typename T>
-constexpr ClampedNumeric<typename UnderlyingType<T>::type> MakeClampedNum(
- const T value) {
+constexpr ClampedNumeric<UnderlyingType<T>> MakeClampedNum(T value) {
return value;
}
// These implement the variadic wrapper for the math operations.
-template <template <typename, typename, typename> class M,
- typename L,
- typename R>
+template <template <typename, typename> class M, typename L, typename R>
constexpr ClampedNumeric<typename MathWrapper<M, L, R>::type> ClampMathOp(
- const L lhs,
- const R rhs) {
+ L lhs,
+ R rhs) {
using Math = typename MathWrapper<M, L, R>::math;
return ClampedNumeric<typename Math::result_type>::template MathOp<M>(lhs,
rhs);
}
// General purpose wrapper template for arithmetic operations.
-template <template <typename, typename, typename> class M,
+template <template <typename, typename> class M,
typename L,
typename R,
typename... Args>
-constexpr auto ClampMathOp(const L lhs, const R rhs, const Args... args) {
+constexpr auto ClampMathOp(L lhs, R rhs, Args... args) {
return ClampMathOp<M>(ClampMathOp<M>(lhs, rhs), args...);
}
diff --git a/core/fxcrt/numerics/clamped_math_impl.h b/core/fxcrt/numerics/clamped_math_impl.h
index 75ca8c6..7d262ea 100644
--- a/core/fxcrt/numerics/clamped_math_impl.h
+++ b/core/fxcrt/numerics/clamped_math_impl.h
@@ -5,49 +5,43 @@
#ifndef CORE_FXCRT_NUMERICS_CLAMPED_MATH_IMPL_H_
#define CORE_FXCRT_NUMERICS_CLAMPED_MATH_IMPL_H_
-#include <stddef.h>
-#include <stdint.h>
+// IWYU pragma: private, include "core/fxcrt/numerics/clamped_math.h"
-#include <climits>
-#include <cmath>
-#include <cstdlib>
+#include <concepts>
#include <limits>
#include <type_traits>
#include "core/fxcrt/numerics/checked_math.h"
#include "core/fxcrt/numerics/safe_conversions.h"
-#include "core/fxcrt/numerics/safe_math_shared_impl.h"
+#include "core/fxcrt/numerics/safe_math_shared_impl.h" // IWYU pragma: export
namespace pdfium {
namespace internal {
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_signed<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::signed_integral<T>)
constexpr T SaturatedNegWrapper(T value) {
- return IsConstantEvaluated() || !ClampedNegFastOp<T>::is_supported
+ return std::is_constant_evaluated() || !ClampedNegFastOp<T>::is_supported
? (NegateWrapper(value) != std::numeric_limits<T>::lowest()
? NegateWrapper(value)
: std::numeric_limits<T>::max())
: ClampedNegFastOp<T>::Do(value);
}
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value &&
- !std::is_signed<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::unsigned_integral<T>)
constexpr T SaturatedNegWrapper(T value) {
return T(0);
}
-template <
- typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::floating_point<T>)
constexpr T SaturatedNegWrapper(T value) {
return -value;
}
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::integral<T>)
constexpr T SaturatedAbsWrapper(T value) {
// The calculation below is a static identity for unsigned types, but for
// signed integer types it provides a non-branching, saturated absolute value.
@@ -62,104 +56,89 @@
IsValueNegative<T>(static_cast<T>(SafeUnsignedAbs(value))));
}
-template <
- typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::floating_point<T>)
constexpr T SaturatedAbsWrapper(T value) {
return value < 0 ? -value : value;
}
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedAddOp {};
template <typename T, typename U>
-struct ClampedAddOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedAddOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V = result_type>
+ requires(std::same_as<V, result_type> || kIsTypeInRangeForNumericType<U, V>)
static constexpr V Do(T x, U y) {
- if (!IsConstantEvaluated() && ClampedAddFastOp<T, U>::is_supported) {
+ if (!std::is_constant_evaluated() && ClampedAddFastOp<T, U>::is_supported) {
return ClampedAddFastOp<T, U>::template Do<V>(x, y);
}
-
- static_assert(std::is_same<V, result_type>::value ||
- IsTypeInRangeForNumericType<U, V>::value,
- "The saturation result cannot be determined from the "
- "provided types.");
const V saturated = CommonMaxOrMin<V>(IsValueNegative(y));
V result = {};
- return BASE_NUMERICS_LIKELY((CheckedAddOp<T, U>::Do(x, y, &result)))
- ? result
- : saturated;
+ if (CheckedAddOp<T, U>::Do(x, y, &result)) [[likely]] {
+ return result;
+ }
+ return saturated;
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedSubOp {};
template <typename T, typename U>
-struct ClampedSubOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedSubOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V = result_type>
+ requires(std::same_as<V, result_type> || kIsTypeInRangeForNumericType<U, V>)
static constexpr V Do(T x, U y) {
- if (!IsConstantEvaluated() && ClampedSubFastOp<T, U>::is_supported) {
+ if (!std::is_constant_evaluated() && ClampedSubFastOp<T, U>::is_supported) {
return ClampedSubFastOp<T, U>::template Do<V>(x, y);
}
-
- static_assert(std::is_same<V, result_type>::value ||
- IsTypeInRangeForNumericType<U, V>::value,
- "The saturation result cannot be determined from the "
- "provided types.");
const V saturated = CommonMaxOrMin<V>(!IsValueNegative(y));
V result = {};
- return BASE_NUMERICS_LIKELY((CheckedSubOp<T, U>::Do(x, y, &result)))
- ? result
- : saturated;
+ if (CheckedSubOp<T, U>::Do(x, y, &result)) [[likely]] {
+ return result;
+ }
+ return saturated;
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedMulOp {};
template <typename T, typename U>
-struct ClampedMulOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedMulOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
- if (!IsConstantEvaluated() && ClampedMulFastOp<T, U>::is_supported) {
+ if (!std::is_constant_evaluated() && ClampedMulFastOp<T, U>::is_supported) {
return ClampedMulFastOp<T, U>::template Do<V>(x, y);
}
-
V result = {};
const V saturated =
CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
- return BASE_NUMERICS_LIKELY((CheckedMulOp<T, U>::Do(x, y, &result)))
- ? result
- : saturated;
+ if (CheckedMulOp<T, U>::Do(x, y, &result)) [[likely]] {
+ return result;
+ }
+ return saturated;
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedDivOp {};
template <typename T, typename U>
-struct ClampedDivOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedDivOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
V result = {};
- if (BASE_NUMERICS_LIKELY((CheckedDivOp<T, U>::Do(x, y, &result)))) {
+ if ((CheckedDivOp<T, U>::Do(x, y, &result))) [[likely]] {
return result;
}
// Saturation goes to max, min, or NaN (if x is zero).
@@ -168,43 +147,39 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedModOp {};
template <typename T, typename U>
-struct ClampedModOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedModOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
V result = {};
- return BASE_NUMERICS_LIKELY((CheckedModOp<T, U>::Do(x, y, &result)))
- ? result
- : x;
+ if (CheckedModOp<T, U>::Do(x, y, &result)) [[likely]] {
+ return result;
+ }
+ return x;
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedLshOp {};
// Left shift. Non-zero values saturate in the direction of the sign. A zero
// shifted by any value always results in zero.
template <typename T, typename U>
-struct ClampedLshOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
+ requires(std::integral<T> && std::unsigned_integral<U>)
+struct ClampedLshOp<T, U> {
using result_type = T;
template <typename V = result_type>
static constexpr V Do(T x, U shift) {
- static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
- if (BASE_NUMERICS_LIKELY(shift < std::numeric_limits<T>::digits)) {
+ if (shift < std::numeric_limits<T>::digits) [[likely]] {
// Shift as unsigned to avoid undefined behavior.
V result = static_cast<V>(as_unsigned(x) << shift);
// If the shift can be reversed, we know it was valid.
- if (BASE_NUMERICS_LIKELY(result >> shift == x)) {
+ if (result >> shift == x) [[likely]] {
return result;
}
}
@@ -212,87 +187,73 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedRshOp {};
// Right shift. Negative values saturate to -1. Positive or 0 saturates to 0.
template <typename T, typename U>
-struct ClampedRshOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
+ requires(std::integral<T> && std::unsigned_integral<U>)
+struct ClampedRshOp<T, U> {
using result_type = T;
template <typename V = result_type>
static constexpr V Do(T x, U shift) {
- static_assert(!std::is_signed<U>::value, "Shift value must be unsigned.");
// Signed right shift is odd, because it saturates to -1 or 0.
const V saturated = as_unsigned(V(0)) - IsValueNegative(x);
- return BASE_NUMERICS_LIKELY(shift < IntegerBitsPlusSign<T>::value)
- ? saturated_cast<V>(x >> shift)
- : saturated;
+ if (shift < kIntegerBitsPlusSign<T>) [[likely]] {
+ return saturated_cast<V>(x >> shift);
+ }
+ return saturated;
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedAndOp {};
template <typename T, typename U>
-struct ClampedAndOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedAndOp<T, U> {
+ using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) & static_cast<result_type>(y);
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedOrOp {};
// For simplicity we promote to unsigned integers.
template <typename T, typename U>
-struct ClampedOrOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedOrOp<T, U> {
+ using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) | static_cast<result_type>(y);
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedXorOp {};
// For simplicity we support only unsigned integers.
template <typename T, typename U>
-struct ClampedXorOp<T,
- U,
- typename std::enable_if<std::is_integral<T>::value &&
- std::is_integral<U>::value>::type> {
- using result_type = typename std::make_unsigned<
- typename MaxExponentPromotion<T, U>::type>::type;
+ requires(std::integral<T> && std::integral<U>)
+struct ClampedXorOp<T, U> {
+ using result_type = std::make_unsigned_t<MaxExponentPromotion<T, U>>;
template <typename V>
static constexpr V Do(T x, U y) {
return static_cast<result_type>(x) ^ static_cast<result_type>(y);
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedMaxOp {};
template <typename T, typename U>
-struct ClampedMaxOp<
- T,
- U,
- typename std::enable_if<std::is_arithmetic<T>::value &&
- std::is_arithmetic<U>::value>::type> {
- using result_type = typename MaxExponentPromotion<T, U>::type;
+ requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct ClampedMaxOp<T, U> {
+ using result_type = MaxExponentPromotion<T, U>;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
return IsGreater<T, U>::Test(x, y) ? saturated_cast<V>(x)
@@ -300,16 +261,13 @@
}
};
-template <typename T, typename U, class Enable = void>
+template <typename T, typename U>
struct ClampedMinOp {};
template <typename T, typename U>
-struct ClampedMinOp<
- T,
- U,
- typename std::enable_if<std::is_arithmetic<T>::value &&
- std::is_arithmetic<U>::value>::type> {
- using result_type = typename LowestValuePromotion<T, U>::type;
+ requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<U>)
+struct ClampedMinOp<T, U> {
+ using result_type = LowestValuePromotion<T, U>;
template <typename V = result_type>
static constexpr V Do(T x, U y) {
return IsLess<T, U>::Test(x, y) ? saturated_cast<V>(x)
@@ -319,17 +277,15 @@
// This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
-#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
- template <typename T, typename U> \
- struct Clamped##NAME##Op< \
- T, U, \
- typename std::enable_if<std::is_floating_point<T>::value || \
- std::is_floating_point<U>::value>::type> { \
- using result_type = typename MaxExponentPromotion<T, U>::type; \
- template <typename V = result_type> \
- static constexpr V Do(T x, U y) { \
- return saturated_cast<V>(x OP y); \
- } \
+#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
+ template <typename T, typename U> \
+ requires(std::floating_point<T> || std::floating_point<U>) \
+ struct Clamped##NAME##Op<T, U> { \
+ using result_type = MaxExponentPromotion<T, U>; \
+ template <typename V = result_type> \
+ static constexpr V Do(T x, U y) { \
+ return saturated_cast<V>(x OP y); \
+ } \
};
BASE_FLOAT_ARITHMETIC_OPS(Add, +)
diff --git a/core/fxcrt/numerics/integral_constant_like.h b/core/fxcrt/numerics/integral_constant_like.h
new file mode 100644
index 0000000..d3ab575
--- /dev/null
+++ b/core/fxcrt/numerics/integral_constant_like.h
@@ -0,0 +1,25 @@
+// Copyright 2025 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CORE_FXCRT_NUMERICS_INTEGRAL_CONSTANT_LIKE_H_
+#define CORE_FXCRT_NUMERICS_INTEGRAL_CONSTANT_LIKE_H_
+
+#include <concepts>
+#include <type_traits>
+
+namespace pdfium {
+
+// Exposition-only concept from [span.syn]
+template <typename T>
+concept IntegralConstantLike =
+ std::is_integral_v<decltype(T::value)> &&
+ !std::is_same_v<bool, std::remove_const_t<decltype(T::value)>> &&
+ std::convertible_to<T, decltype(T::value)> &&
+ std::equality_comparable_with<T, decltype(T::value)> &&
+ std::bool_constant<T() == T::value>::value &&
+ std::bool_constant<static_cast<decltype(T::value)>(T()) == T::value>::value;
+
+} // namespace pdfium
+
+#endif // CORE_FXCRT_NUMERICS_INTEGRAL_CONSTANT_LIKE_H_
diff --git a/core/fxcrt/numerics/safe_conversions.h b/core/fxcrt/numerics/safe_conversions.h
index 70a82fc..7041c81 100644
--- a/core/fxcrt/numerics/safe_conversions.h
+++ b/core/fxcrt/numerics/safe_conversions.h
@@ -8,13 +8,14 @@
#include <stddef.h>
#include <cmath>
+#include <concepts>
#include <limits>
#include <type_traits>
-#include "core/fxcrt/numerics/safe_conversions_impl.h"
+#include "core/fxcrt/numerics/safe_conversions_impl.h" // IWYU pragma: export
#if defined(__ARMEL__) && !defined(__native_client__)
-#include "core/fxcrt/numerics/safe_conversions_arm_impl.h"
+#include "core/fxcrt/numerics/safe_conversions_arm_impl.h" // IWYU pragma: export
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_CONVERSIONS (0)
@@ -37,10 +38,10 @@
// The following special case a few specific integer conversions where we can
// eke out better performance than range checking.
-template <typename Dst, typename Src, typename Enable = void>
+template <typename Dst, typename Src>
struct IsValueInRangeFastOp {
static constexpr bool is_supported = false;
- static constexpr bool Do(Src value) {
+ static constexpr bool Do(Src) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<bool>();
}
@@ -48,13 +49,9 @@
// Signed to signed range comparison.
template <typename Dst, typename Src>
-struct IsValueInRangeFastOp<
- Dst,
- Src,
- typename std::enable_if<
- std::is_integral<Dst>::value && std::is_integral<Src>::value &&
- std::is_signed<Dst>::value && std::is_signed<Src>::value &&
- !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+ requires(std::signed_integral<Dst> && std::signed_integral<Src> &&
+ !kIsTypeInRangeForNumericType<Dst, Src>)
+struct IsValueInRangeFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr bool Do(Src value) {
@@ -66,32 +63,30 @@
// Signed to unsigned range comparison.
template <typename Dst, typename Src>
-struct IsValueInRangeFastOp<
- Dst,
- Src,
- typename std::enable_if<
- std::is_integral<Dst>::value && std::is_integral<Src>::value &&
- !std::is_signed<Dst>::value && std::is_signed<Src>::value &&
- !IsTypeInRangeForNumericType<Dst, Src>::value>::type> {
+ requires(std::unsigned_integral<Dst> && std::signed_integral<Src> &&
+ !kIsTypeInRangeForNumericType<Dst, Src>)
+struct IsValueInRangeFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr bool Do(Src value) {
// We cast a signed as unsigned to overflow negative values to the top,
// then compare against whichever maximum is smaller, as our upper bound.
- return as_unsigned(value) <= as_unsigned(CommonMax<Src, Dst>());
+ return as_unsigned(value) <= as_unsigned(kCommonMax<Src, Dst>);
}
};
// Convenience function that returns true if the supplied value is in range
// for the destination type.
template <typename Dst, typename Src>
+ requires(IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
+ std::numeric_limits<Dst>::lowest() < std::numeric_limits<Dst>::max())
constexpr bool IsValueInRangeForNumericType(Src value) {
- using SrcType = typename internal::UnderlyingType<Src>::type;
+ using SrcType = UnderlyingType<Src>;
+ const auto underlying_value = static_cast<SrcType>(value);
return internal::IsValueInRangeFastOp<Dst, SrcType>::is_supported
? internal::IsValueInRangeFastOp<Dst, SrcType>::Do(
- static_cast<SrcType>(value))
- : internal::DstRangeRelationToSrcRange<Dst>(
- static_cast<SrcType>(value))
+ underlying_value)
+ : internal::DstRangeRelationToSrcRange<Dst>(underlying_value)
.IsValid();
}
@@ -101,13 +96,15 @@
template <typename Dst,
class CheckHandler = internal::CheckOnFailure,
typename Src>
+ requires(IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
+ std::numeric_limits<Dst>::lowest() < std::numeric_limits<Dst>::max())
constexpr Dst checked_cast(Src value) {
// This throws a compile-time error on evaluating the constexpr if it can be
// determined at compile-time as failing, otherwise it will CHECK at runtime.
- using SrcType = typename internal::UnderlyingType<Src>::type;
- return BASE_NUMERICS_LIKELY((IsValueInRangeForNumericType<Dst>(value)))
- ? static_cast<Dst>(static_cast<SrcType>(value))
- : CheckHandler::template HandleFailure<Dst>();
+ if (IsValueInRangeForNumericType<Dst>(value)) [[likely]] {
+ return static_cast<Dst>(static_cast<UnderlyingType<Src>>(value));
+ }
+ return CheckHandler::template HandleFailure<Dst>();
}
// Default boundaries for integral/float: max/infinity, lowest/-infinity, 0/NaN.
@@ -148,7 +145,7 @@
? (!constraint.IsUnderflowFlagSet() ? static_cast<Dst>(value)
: S<Dst>::Underflow())
// Skip this check for integral Src, which cannot be NaN.
- : (std::is_integral<Src>::value || !constraint.IsUnderflowFlagSet()
+ : (std::is_integral_v<Src> || !constraint.IsUnderflowFlagSet()
? S<Dst>::Overflow()
: S<Dst>::NaN());
}
@@ -156,22 +153,19 @@
// We can reduce the number of conditions and get slightly better performance
// for normal signed and unsigned integer ranges. And in the specific case of
// Arm, we can use the optimized saturation instructions.
-template <typename Dst, typename Src, typename Enable = void>
+template <typename Dst, typename Src>
struct SaturateFastOp {
static constexpr bool is_supported = false;
- static constexpr Dst Do(Src value) {
+ static constexpr Dst Do(Src) {
// Force a compile failure if instantiated.
return CheckOnFailure::template HandleFailure<Dst>();
}
};
template <typename Dst, typename Src>
-struct SaturateFastOp<
- Dst,
- Src,
- typename std::enable_if<std::is_integral<Src>::value &&
- std::is_integral<Dst>::value &&
- SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
+ requires(std::integral<Src> && std::integral<Dst> &&
+ SaturateFastAsmOp<Dst, Src>::is_supported)
+struct SaturateFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr Dst Do(Src value) {
return SaturateFastAsmOp<Dst, Src>::Do(value);
@@ -179,23 +173,21 @@
};
template <typename Dst, typename Src>
-struct SaturateFastOp<
- Dst,
- Src,
- typename std::enable_if<std::is_integral<Src>::value &&
- std::is_integral<Dst>::value &&
- !SaturateFastAsmOp<Dst, Src>::is_supported>::type> {
+ requires(std::integral<Src> && std::integral<Dst> &&
+ !SaturateFastAsmOp<Dst, Src>::is_supported)
+struct SaturateFastOp<Dst, Src> {
static constexpr bool is_supported = true;
static constexpr Dst Do(Src value) {
// The exact order of the following is structured to hit the correct
// optimization heuristics across compilers. Do not change without
// checking the emitted code.
const Dst saturated = CommonMaxOrMin<Dst, Src>(
- IsMaxInRangeForNumericType<Dst, Src>() ||
- (!IsMinInRangeForNumericType<Dst, Src>() && IsValueNegative(value)));
- return BASE_NUMERICS_LIKELY(IsValueInRangeForNumericType<Dst>(value))
- ? static_cast<Dst>(value)
- : saturated;
+ kIsMaxInRangeForNumericType<Dst, Src> ||
+ (!kIsMinInRangeForNumericType<Dst, Src> && IsValueNegative(value)));
+ if (IsValueInRangeForNumericType<Dst>(value)) [[likely]] {
+ return static_cast<Dst>(value);
+ }
+ return saturated;
}
};
@@ -207,56 +199,47 @@
template <typename> class SaturationHandler = SaturationDefaultLimits,
typename Src>
constexpr Dst saturated_cast(Src value) {
- using SrcType = typename UnderlyingType<Src>::type;
- return !IsConstantEvaluated() && SaturateFastOp<Dst, SrcType>::is_supported &&
- std::is_same<SaturationHandler<Dst>,
- SaturationDefaultLimits<Dst>>::value
- ? SaturateFastOp<Dst, SrcType>::Do(static_cast<SrcType>(value))
+ using SrcType = UnderlyingType<Src>;
+ const auto underlying_value = static_cast<SrcType>(value);
+ return !std::is_constant_evaluated() &&
+ SaturateFastOp<Dst, SrcType>::is_supported &&
+ std::is_same_v<SaturationHandler<Dst>,
+ SaturationDefaultLimits<Dst>>
+ ? SaturateFastOp<Dst, SrcType>::Do(underlying_value)
: saturated_cast_impl<Dst, SaturationHandler, SrcType>(
- static_cast<SrcType>(value),
+ underlying_value,
DstRangeRelationToSrcRange<Dst, SaturationHandler, SrcType>(
- static_cast<SrcType>(value)));
+ underlying_value));
}
// strict_cast<> is analogous to static_cast<> for numeric types, except that
// it will cause a compile failure if the destination type is not large enough
// to contain any value in the source type. It performs no runtime checking.
-template <typename Dst, typename Src>
+template <typename Dst, typename Src, typename SrcType = UnderlyingType<Src>>
+ requires(
+ IsNumeric<Src> && std::is_arithmetic_v<Dst> &&
+ // If you got here from a compiler error, it's because you tried to assign
+ // from a source type to a destination type that has insufficient range.
+ // The solution may be to change the destination type you're assigning to,
+ // and use one large enough to represent the source.
+ // Alternatively, you may be better served with the checked_cast<> or
+ // saturated_cast<> template functions for your particular use case.
+ kStaticDstRangeRelationToSrcRange<Dst, SrcType> ==
+ NumericRangeRepresentation::kContained)
constexpr Dst strict_cast(Src value) {
- using SrcType = typename UnderlyingType<Src>::type;
- static_assert(UnderlyingType<Src>::is_numeric, "Argument must be numeric.");
- static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
-
- // If you got here from a compiler error, it's because you tried to assign
- // from a source type to a destination type that has insufficient range.
- // The solution may be to change the destination type you're assigning to,
- // and use one large enough to represent the source.
- // Alternatively, you may be better served with the checked_cast<> or
- // saturated_cast<> template functions for your particular use case.
- static_assert(StaticDstRangeRelationToSrcRange<Dst, SrcType>::value ==
- NUMERIC_RANGE_CONTAINED,
- "The source type is out of range for the destination type. "
- "Please see strict_cast<> comments for more information.");
-
return static_cast<Dst>(static_cast<SrcType>(value));
}
// Some wrappers to statically check that a type is in range.
-template <typename Dst, typename Src, class Enable = void>
-struct IsNumericRangeContained {
- static constexpr bool value = false;
-};
+template <typename Dst, typename Src>
+inline constexpr bool kIsNumericRangeContained = false;
template <typename Dst, typename Src>
-struct IsNumericRangeContained<
- Dst,
- Src,
- typename std::enable_if<ArithmeticOrUnderlyingEnum<Dst>::value &&
- ArithmeticOrUnderlyingEnum<Src>::value>::type> {
- static constexpr bool value =
- StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
- NUMERIC_RANGE_CONTAINED;
-};
+ requires(std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Dst>> &&
+ std::is_arithmetic_v<ArithmeticOrUnderlyingEnum<Src>>)
+inline constexpr bool kIsNumericRangeContained<Dst, Src> =
+ kStaticDstRangeRelationToSrcRange<Dst, Src> ==
+ NumericRangeRepresentation::kContained;
// StrictNumeric implements compile time range checking between numeric types by
// wrapping assignment operations in a strict_cast. This class is intended to be
@@ -269,6 +252,7 @@
// runtime checking of any of the associated mathematical operations. Use
// CheckedNumeric for runtime range checks of the actual value being assigned.
template <typename T>
+ requires std::is_arithmetic_v<T>
class StrictNumeric {
public:
using type = T;
@@ -280,12 +264,6 @@
constexpr StrictNumeric(const StrictNumeric<Src>& rhs)
: value_(strict_cast<T>(rhs.value_)) {}
- // Strictly speaking, this is not necessary, but declaring this allows class
- // template argument deduction to be used so that it is possible to simply
- // write `StrictNumeric(777)` instead of `StrictNumeric<int>(777)`.
- // NOLINTNEXTLINE(google-explicit-constructor)
- constexpr StrictNumeric(T value) : value_(value) {}
-
// This is not an explicit constructor because we implicitly upgrade regular
// numerics to StrictNumerics to make them easier to use.
template <typename Src>
@@ -304,32 +282,38 @@
// to explicitly cast the result to the destination type.
// If none of that works, you may be better served with the checked_cast<> or
// saturated_cast<> template functions for your particular use case.
- template <typename Dst,
- typename std::enable_if<
- IsNumericRangeContained<Dst, T>::value>::type* = nullptr>
- constexpr operator Dst() const {
- return static_cast<typename ArithmeticOrUnderlyingEnum<Dst>::type>(value_);
+ template <typename Dst>
+ requires(kIsNumericRangeContained<Dst, T>)
+ constexpr operator Dst() const { // NOLINT(google-explicit-constructor)
+ return static_cast<ArithmeticOrUnderlyingEnum<Dst>>(value_);
}
+ // Unary negation does not require any conversions.
+ constexpr bool operator!() const { return !value_; }
+
private:
- const T value_;
+ template <typename U>
+ requires std::is_arithmetic_v<U>
+ friend class StrictNumeric;
+
+ T value_;
};
+template <typename T>
+StrictNumeric(T) -> StrictNumeric<T>;
+
// Convenience wrapper returns a StrictNumeric from the provided arithmetic
// type.
template <typename T>
-constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
- const T value) {
+constexpr StrictNumeric<UnderlyingType<T>> MakeStrictNum(const T value) {
return value;
}
-#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
- template <typename L, typename R, \
- typename std::enable_if< \
- internal::Is##CLASS##Op<L, R>::value>::type* = nullptr> \
- constexpr bool operator OP(const L lhs, const R rhs) { \
- return SafeCompare<NAME, typename UnderlyingType<L>::type, \
- typename UnderlyingType<R>::type>(lhs, rhs); \
+#define BASE_NUMERIC_COMPARISON_OPERATORS(CLASS, NAME, OP) \
+ template <typename L, typename R> \
+ requires(internal::Is##CLASS##Op<L, R>) \
+ constexpr bool operator OP(L lhs, R rhs) { \
+ return SafeCompare<NAME, UnderlyingType<L>, UnderlyingType<R>>(lhs, rhs); \
}
BASE_NUMERIC_COMPARISON_OPERATORS(Strict, IsLess, <)
@@ -344,9 +328,9 @@
using internal::as_signed;
using internal::as_unsigned;
using internal::checked_cast;
-using internal::IsTypeInRangeForNumericType;
using internal::IsValueInRangeForNumericType;
using internal::IsValueNegative;
+using internal::kIsTypeInRangeForNumericType;
using internal::MakeStrictNum;
using internal::SafeUnsignedAbs;
using internal::saturated_cast;
@@ -357,29 +341,44 @@
using SizeT = StrictNumeric<size_t>;
// floating -> integral conversions that saturate and thus can actually return
-// an integral type. In most cases, these should be preferred over the std::
-// versions.
-template <typename Dst = int,
- typename Src,
- typename = std::enable_if_t<std::is_integral<Dst>::value &&
- std::is_floating_point<Src>::value>>
+// an integral type.
+//
+// Generally, what you want is saturated_cast<Dst>(std::nearbyint(x)), which
+// rounds correctly according to IEEE-754 (round to nearest, ties go to nearest
+// even number; this avoids bias). If your code is performance-critical
+// and you are sure that you will never overflow, you can use std::lrint()
+// or std::llrint(), which return a long or long long directly.
+//
+// Below are convenience functions around similar patterns, except that
+// they round in nonstandard directions and will generally be slower.
+
+// Rounds towards negative infinity (i.e., down).
+template <typename Dst = int, typename Src>
+ requires(std::integral<Dst> && std::floating_point<Src>)
Dst ClampFloor(Src value) {
return saturated_cast<Dst>(std::floor(value));
}
-template <typename Dst = int,
- typename Src,
- typename = std::enable_if_t<std::is_integral<Dst>::value &&
- std::is_floating_point<Src>::value>>
+
+// Rounds towards positive infinity (i.e., up).
+template <typename Dst = int, typename Src>
+ requires(std::integral<Dst> && std::floating_point<Src>)
Dst ClampCeil(Src value) {
return saturated_cast<Dst>(std::ceil(value));
}
-template <typename Dst = int,
- typename Src,
- typename = std::enable_if_t<std::is_integral<Dst>::value &&
- std::is_floating_point<Src>::value>>
+
+// Rounds towards nearest integer, with ties away from zero.
+// This means that 0.5 will be rounded to 1 and 1.5 will be rounded to 2.
+// Similarly, -0.5 will be rounded to -1 and -1.5 will be rounded to -2.
+//
+// This is normally not what you want accuracy-wise (it introduces a small bias
+// away from zero), and it is not the fastest option, but it is frequently what
+// existing code expects. Compare with saturated_cast<Dst>(std::nearbyint(x))
+// or std::lrint(x), which would round 0.5 and -0.5 to 0 but 1.5 to 2 and
+// -1.5 to -2.
+template <typename Dst = int, typename Src>
+ requires(std::integral<Dst> && std::floating_point<Src>)
Dst ClampRound(Src value) {
- const Src rounded =
- (value >= 0.0f) ? std::floor(value + 0.5f) : std::ceil(value - 0.5f);
+ const Src rounded = std::round(value);
return saturated_cast<Dst>(rounded);
}
diff --git a/core/fxcrt/numerics/safe_conversions_arm_impl.h b/core/fxcrt/numerics/safe_conversions_arm_impl.h
index 2eaf7a8..c85c9a4 100644
--- a/core/fxcrt/numerics/safe_conversions_arm_impl.h
+++ b/core/fxcrt/numerics/safe_conversions_arm_impl.h
@@ -5,8 +5,12 @@
#ifndef CORE_FXCRT_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
#define CORE_FXCRT_NUMERICS_SAFE_CONVERSIONS_ARM_IMPL_H_
-#include <cassert>
-#include <limits>
+// IWYU pragma: private, include "core/fxcrt/numerics/safe_conversions.h"
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <concepts>
#include <type_traits>
#include "core/fxcrt/numerics/safe_conversions_impl.h"
@@ -18,30 +22,28 @@
template <typename Dst, typename Src>
struct SaturateFastAsmOp {
static constexpr bool is_supported =
- kEnableAsmCode && std::is_signed<Src>::value &&
- std::is_integral<Dst>::value && std::is_integral<Src>::value &&
- IntegerBitsPlusSign<Src>::value <= IntegerBitsPlusSign<int32_t>::value &&
- IntegerBitsPlusSign<Dst>::value <= IntegerBitsPlusSign<int32_t>::value &&
- !IsTypeInRangeForNumericType<Dst, Src>::value;
+ kEnableAsmCode && std::signed_integral<Src> && std::integral<Dst> &&
+ kIntegerBitsPlusSign<Src> <= kIntegerBitsPlusSign<int32_t> &&
+ kIntegerBitsPlusSign<Dst> <= kIntegerBitsPlusSign<int32_t> &&
+ !kIsTypeInRangeForNumericType<Dst, Src>;
__attribute__((always_inline)) static Dst Do(Src value) {
int32_t src = value;
- typename std::conditional<std::is_signed<Dst>::value, int32_t,
- uint32_t>::type result;
- if (std::is_signed<Dst>::value) {
+ if constexpr (std::is_signed_v<Dst>) {
+ int32_t result;
asm("ssat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
- : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value <= 32
- ? IntegerBitsPlusSign<Dst>::value
- : 32));
+ : [src] "r"(src), [shift] "n"(
+ std::min(kIntegerBitsPlusSign<Dst>, 32)));
+ return static_cast<Dst>(result);
} else {
+ uint32_t result;
asm("usat %[dst], %[shift], %[src]"
: [dst] "=r"(result)
- : [src] "r"(src), [shift] "n"(IntegerBitsPlusSign<Dst>::value < 32
- ? IntegerBitsPlusSign<Dst>::value
- : 31));
+ : [src] "r"(src), [shift] "n"(
+ std::min(kIntegerBitsPlusSign<Dst>, 31)));
+ return static_cast<Dst>(result);
}
- return static_cast<Dst>(result);
}
};
diff --git a/core/fxcrt/numerics/safe_conversions_impl.h b/core/fxcrt/numerics/safe_conversions_impl.h
index 4dae1ed..b510787 100644
--- a/core/fxcrt/numerics/safe_conversions_impl.h
+++ b/core/fxcrt/numerics/safe_conversions_impl.h
@@ -5,72 +5,54 @@
#ifndef CORE_FXCRT_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
#define CORE_FXCRT_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
+// IWYU pragma: private, include "core/fxcrt/numerics/safe_conversions.h"
+
+#include <stddef.h>
#include <stdint.h>
+#include <concepts>
#include <limits>
#include <type_traits>
+#include <utility>
-#if defined(__GNUC__) || defined(__clang__)
-#define BASE_NUMERICS_LIKELY(x) __builtin_expect(!!(x), 1)
-#define BASE_NUMERICS_UNLIKELY(x) __builtin_expect(!!(x), 0)
-#else
-#define BASE_NUMERICS_LIKELY(x) (x)
-#define BASE_NUMERICS_UNLIKELY(x) (x)
-#endif
+#include "core/fxcrt/numerics/integral_constant_like.h"
-namespace pdfium {
-namespace internal {
+namespace pdfium::internal {
// The std library doesn't provide a binary max_exponent for integers, however
// we can compute an analog using std::numeric_limits<>::digits.
template <typename NumericType>
-struct MaxExponent {
- static const int value = std::is_floating_point<NumericType>::value
- ? std::numeric_limits<NumericType>::max_exponent
- : std::numeric_limits<NumericType>::digits + 1;
-};
+inline constexpr int kMaxExponent =
+ std::is_floating_point_v<NumericType>
+ ? std::numeric_limits<NumericType>::max_exponent
+ : std::numeric_limits<NumericType>::digits + 1;
// The number of bits (including the sign) in an integer. Eliminates sizeof
// hacks.
template <typename NumericType>
-struct IntegerBitsPlusSign {
- static const int value = std::numeric_limits<NumericType>::digits +
- std::is_signed<NumericType>::value;
-};
-
-// Helper templates for integer manipulations.
-
-template <typename Integer>
-struct PositionOfSignBit {
- static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
-};
+inline constexpr int kIntegerBitsPlusSign =
+ std::numeric_limits<NumericType>::digits + std::is_signed_v<NumericType>;
// Determines if a numeric value is negative without throwing compiler
// warnings on: unsigned(value) < 0.
-template <typename T,
- typename std::enable_if<std::is_signed<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::is_arithmetic_v<T>)
constexpr bool IsValueNegative(T value) {
- static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
- return value < 0;
-}
-
-template <typename T,
- typename std::enable_if<!std::is_signed<T>::value>::type* = nullptr>
-constexpr bool IsValueNegative(T) {
- static_assert(std::is_arithmetic<T>::value, "Argument must be numeric.");
- return false;
+ if constexpr (std::is_signed_v<T>) {
+ return value < 0;
+ } else {
+ return false;
+ }
}
// This performs a fast negation, returning a signed value. It works on unsigned
// arguments, but probably doesn't do what you want for any unsigned value
// larger than max / 2 + 1 (i.e. signed min cast to unsigned).
template <typename T>
-constexpr typename std::make_signed<T>::type ConditionalNegate(
- T x,
- bool is_negative) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- using SignedT = typename std::make_signed<T>::type;
- using UnsignedT = typename std::make_unsigned<T>::type;
+ requires std::is_integral_v<T>
+constexpr auto ConditionalNegate(T x, bool is_negative) {
+ using SignedT = std::make_signed_t<T>;
+ using UnsignedT = std::make_unsigned_t<T>;
return static_cast<SignedT>((static_cast<UnsignedT>(x) ^
static_cast<UnsignedT>(-SignedT(is_negative))) +
is_negative);
@@ -78,28 +60,24 @@
// This performs a safe, absolute value via unsigned overflow.
template <typename T>
-constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
- static_assert(std::is_integral<T>::value, "Type must be integral");
- using UnsignedT = typename std::make_unsigned<T>::type;
+ requires std::is_integral_v<T>
+constexpr auto SafeUnsignedAbs(T value) {
+ using UnsignedT = std::make_unsigned_t<T>;
return IsValueNegative(value)
? static_cast<UnsignedT>(0u - static_cast<UnsignedT>(value))
: static_cast<UnsignedT>(value);
}
-// TODO(jschuh): Switch to std::is_constant_evaluated() once C++20 is supported.
-// Alternately, the usage could be restructured for "consteval if" in C++23.
-#define IsConstantEvaluated() (__builtin_is_constant_evaluated())
-
// TODO(jschuh): Debug builds don't reliably propagate constants, so we restrict
// some accelerated runtime paths to release builds until this can be forced
// with consteval support in C++20 or C++23.
#if defined(NDEBUG)
-constexpr bool kEnableAsmCode = true;
+inline constexpr bool kEnableAsmCode = true;
#else
-constexpr bool kEnableAsmCode = false;
+inline constexpr bool kEnableAsmCode = false;
#endif
-// Forces a crash, like a CHECK(false). Used for numeric boundary errors.
+// Forces a crash, like a NOTREACHED(). Used for numeric boundary errors.
// Also used in a constexpr template to trigger a compilation failure on
// an error condition.
struct CheckOnFailure {
@@ -116,92 +94,76 @@
}
};
-enum IntegerRepresentation {
- INTEGER_REPRESENTATION_UNSIGNED,
- INTEGER_REPRESENTATION_SIGNED
-};
+enum class IntegerRepresentation { kUnsigned, kSigned };
// A range for a given nunmeric Src type is contained for a given numeric Dst
// type if both numeric_limits<Src>::max() <= numeric_limits<Dst>::max() and
// numeric_limits<Src>::lowest() >= numeric_limits<Dst>::lowest() are true.
// We implement this as template specializations rather than simple static
// comparisons to ensure type correctness in our comparisons.
-enum NumericRangeRepresentation {
- NUMERIC_RANGE_NOT_CONTAINED,
- NUMERIC_RANGE_CONTAINED
-};
+enum class NumericRangeRepresentation { kNotContained, kContained };
// Helper templates to statically determine if our destination type can contain
// maximum and minimum values represented by the source type.
+// Default case, used for same sign: Dst is guaranteed to contain Src only if
+// its range is equal or larger.
template <typename Dst,
typename Src,
- IntegerRepresentation DstSign = std::is_signed<Dst>::value
- ? INTEGER_REPRESENTATION_SIGNED
- : INTEGER_REPRESENTATION_UNSIGNED,
- IntegerRepresentation SrcSign = std::is_signed<Src>::value
- ? INTEGER_REPRESENTATION_SIGNED
- : INTEGER_REPRESENTATION_UNSIGNED>
-struct StaticDstRangeRelationToSrcRange;
-
-// Same sign: Dst is guaranteed to contain Src only if its range is equal or
-// larger.
-template <typename Dst, typename Src, IntegerRepresentation Sign>
-struct StaticDstRangeRelationToSrcRange<Dst, Src, Sign, Sign> {
- static const NumericRangeRepresentation value =
- MaxExponent<Dst>::value >= MaxExponent<Src>::value
- ? NUMERIC_RANGE_CONTAINED
- : NUMERIC_RANGE_NOT_CONTAINED;
-};
+ IntegerRepresentation DstSign =
+ std::is_signed_v<Dst> ? IntegerRepresentation::kSigned
+ : IntegerRepresentation::kUnsigned,
+ IntegerRepresentation SrcSign =
+ std::is_signed_v<Src> ? IntegerRepresentation::kSigned
+ : IntegerRepresentation::kUnsigned>
+inline constexpr auto kStaticDstRangeRelationToSrcRange =
+ kMaxExponent<Dst> >= kMaxExponent<Src>
+ ? NumericRangeRepresentation::kContained
+ : NumericRangeRepresentation::kNotContained;
// Unsigned to signed: Dst is guaranteed to contain source only if its range is
// larger.
template <typename Dst, typename Src>
-struct StaticDstRangeRelationToSrcRange<Dst,
- Src,
- INTEGER_REPRESENTATION_SIGNED,
- INTEGER_REPRESENTATION_UNSIGNED> {
- static const NumericRangeRepresentation value =
- MaxExponent<Dst>::value > MaxExponent<Src>::value
- ? NUMERIC_RANGE_CONTAINED
- : NUMERIC_RANGE_NOT_CONTAINED;
-};
+inline constexpr auto
+ kStaticDstRangeRelationToSrcRange<Dst,
+ Src,
+ IntegerRepresentation::kSigned,
+ IntegerRepresentation::kUnsigned> =
+ kMaxExponent<Dst> > kMaxExponent<Src>
+ ? NumericRangeRepresentation::kContained
+ : NumericRangeRepresentation::kNotContained;
// Signed to unsigned: Dst cannot be statically determined to contain Src.
template <typename Dst, typename Src>
-struct StaticDstRangeRelationToSrcRange<Dst,
- Src,
- INTEGER_REPRESENTATION_UNSIGNED,
- INTEGER_REPRESENTATION_SIGNED> {
- static const NumericRangeRepresentation value = NUMERIC_RANGE_NOT_CONTAINED;
-};
+inline constexpr auto
+ kStaticDstRangeRelationToSrcRange<Dst,
+ Src,
+ IntegerRepresentation::kUnsigned,
+ IntegerRepresentation::kSigned> =
+ NumericRangeRepresentation::kNotContained;
// This class wraps the range constraints as separate booleans so the compiler
// can identify constants and eliminate unused code paths.
class RangeCheck {
public:
+ constexpr RangeCheck() = default;
constexpr RangeCheck(bool is_in_lower_bound, bool is_in_upper_bound)
: is_underflow_(!is_in_lower_bound), is_overflow_(!is_in_upper_bound) {}
- constexpr RangeCheck() : is_underflow_(false), is_overflow_(false) {}
+
+ constexpr bool operator==(const RangeCheck& rhs) const = default;
+
constexpr bool IsValid() const { return !is_overflow_ && !is_underflow_; }
constexpr bool IsInvalid() const { return is_overflow_ && is_underflow_; }
constexpr bool IsOverflow() const { return is_overflow_ && !is_underflow_; }
constexpr bool IsUnderflow() const { return !is_overflow_ && is_underflow_; }
constexpr bool IsOverflowFlagSet() const { return is_overflow_; }
constexpr bool IsUnderflowFlagSet() const { return is_underflow_; }
- constexpr bool operator==(const RangeCheck rhs) const {
- return is_underflow_ == rhs.is_underflow_ &&
- is_overflow_ == rhs.is_overflow_;
- }
- constexpr bool operator!=(const RangeCheck rhs) const {
- return !(*this == rhs);
- }
private:
// Do not change the order of these member variables. The integral conversion
// optimization depends on this exact order.
- const bool is_underflow_;
- const bool is_overflow_;
+ const bool is_underflow_ = false;
+ const bool is_overflow_ = false;
};
// The following helper template addresses a corner case in range checks for
@@ -228,71 +190,54 @@
template <typename Dst, typename Src, template <typename> class Bounds>
struct NarrowingRange {
using SrcLimits = std::numeric_limits<Src>;
- using DstLimits = typename std::numeric_limits<Dst>;
+ using DstLimits = std::numeric_limits<Dst>;
// Computes the mask required to make an accurate comparison between types.
- static const int kShift =
- (MaxExponent<Src>::value > MaxExponent<Dst>::value &&
- SrcLimits::digits < DstLimits::digits)
- ? (DstLimits::digits - SrcLimits::digits)
- : 0;
- template <
- typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+ static constexpr int kShift = (kMaxExponent<Src> > kMaxExponent<Dst> &&
+ SrcLimits::digits < DstLimits::digits)
+ ? (DstLimits::digits - SrcLimits::digits)
+ : 0;
+ template <typename T>
+ requires(std::same_as<T, Dst> &&
+ ((std::integral<T> && kShift < DstLimits::digits) ||
+ (std::floating_point<T> && kShift == 0)))
// Masks out the integer bits that are beyond the precision of the
// intermediate type used for comparison.
static constexpr T Adjust(T value) {
- static_assert(std::is_same<T, Dst>::value, "");
- static_assert(kShift < DstLimits::digits, "");
- using UnsignedDst = typename std::make_unsigned_t<T>;
- return static_cast<T>(ConditionalNegate(
- SafeUnsignedAbs(value) & ~((UnsignedDst{1} << kShift) - UnsignedDst{1}),
- IsValueNegative(value)));
- }
-
- template <typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* =
- nullptr>
- static constexpr T Adjust(T value) {
- static_assert(std::is_same<T, Dst>::value, "");
- static_assert(kShift == 0, "");
- return value;
+ if constexpr (std::integral<T>) {
+ using UnsignedDst = typename std::make_unsigned_t<T>;
+ return static_cast<T>(
+ ConditionalNegate(SafeUnsignedAbs(value) &
+ ~((UnsignedDst{1} << kShift) - UnsignedDst{1}),
+ IsValueNegative(value)));
+ } else {
+ return value;
+ }
}
static constexpr Dst max() { return Adjust(Bounds<Dst>::max()); }
static constexpr Dst lowest() { return Adjust(Bounds<Dst>::lowest()); }
};
-template <typename Dst,
- typename Src,
- template <typename> class Bounds,
- IntegerRepresentation DstSign = std::is_signed<Dst>::value
- ? INTEGER_REPRESENTATION_SIGNED
- : INTEGER_REPRESENTATION_UNSIGNED,
- IntegerRepresentation SrcSign = std::is_signed<Src>::value
- ? INTEGER_REPRESENTATION_SIGNED
- : INTEGER_REPRESENTATION_UNSIGNED,
- NumericRangeRepresentation DstRange =
- StaticDstRangeRelationToSrcRange<Dst, Src>::value>
-struct DstRangeRelationToSrcRangeImpl;
-
// The following templates are for ranges that must be verified at runtime. We
// split it into checks based on signedness to avoid confusing casts and
// compiler warnings on signed an unsigned comparisons.
-// Same sign narrowing: The range is contained for normal limits.
+// Default case, used for same sign narrowing: The range is contained for normal
+// limits.
template <typename Dst,
typename Src,
template <typename> class Bounds,
- IntegerRepresentation DstSign,
- IntegerRepresentation SrcSign>
-struct DstRangeRelationToSrcRangeImpl<Dst,
- Src,
- Bounds,
- DstSign,
- SrcSign,
- NUMERIC_RANGE_CONTAINED> {
+ IntegerRepresentation DstSign =
+ std::is_signed_v<Dst> ? IntegerRepresentation::kSigned
+ : IntegerRepresentation::kUnsigned,
+ IntegerRepresentation SrcSign =
+ std::is_signed_v<Src> ? IntegerRepresentation::kSigned
+ : IntegerRepresentation::kUnsigned,
+ NumericRangeRepresentation DstRange =
+ kStaticDstRangeRelationToSrcRange<Dst, Src>>
+struct DstRangeRelationToSrcRangeImpl {
static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
@@ -307,12 +252,13 @@
// Signed to signed narrowing: Both the upper and lower boundaries may be
// exceeded for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
- Src,
- Bounds,
- INTEGER_REPRESENTATION_SIGNED,
- INTEGER_REPRESENTATION_SIGNED,
- NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+ Dst,
+ Src,
+ Bounds,
+ IntegerRepresentation::kSigned,
+ IntegerRepresentation::kSigned,
+ NumericRangeRepresentation::kNotContained> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(value >= DstLimits::lowest(), value <= DstLimits::max());
@@ -322,32 +268,34 @@
// Unsigned to unsigned narrowing: Only the upper bound can be exceeded for
// standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
- Src,
- Bounds,
- INTEGER_REPRESENTATION_UNSIGNED,
- INTEGER_REPRESENTATION_UNSIGNED,
- NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+ Dst,
+ Src,
+ Bounds,
+ IntegerRepresentation::kUnsigned,
+ IntegerRepresentation::kUnsigned,
+ NumericRangeRepresentation::kNotContained> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
return RangeCheck(
- DstLimits::lowest() == Dst(0) || value >= DstLimits::lowest(),
+ DstLimits::lowest() == Dst{0} || value >= DstLimits::lowest(),
value <= DstLimits::max());
}
};
// Unsigned to signed: Only the upper bound can be exceeded for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
- Src,
- Bounds,
- INTEGER_REPRESENTATION_SIGNED,
- INTEGER_REPRESENTATION_UNSIGNED,
- NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+ Dst,
+ Src,
+ Bounds,
+ IntegerRepresentation::kSigned,
+ IntegerRepresentation::kUnsigned,
+ NumericRangeRepresentation::kNotContained> {
static constexpr RangeCheck Check(Src value) {
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
using Promotion = decltype(Src() + Dst());
- return RangeCheck(DstLimits::lowest() <= Dst(0) ||
+ return RangeCheck(DstLimits::lowest() <= Dst{0} ||
static_cast<Promotion>(value) >=
static_cast<Promotion>(DstLimits::lowest()),
static_cast<Promotion>(value) <=
@@ -358,23 +306,24 @@
// Signed to unsigned: The upper boundary may be exceeded for a narrower Dst,
// and any negative value exceeds the lower boundary for standard limits.
template <typename Dst, typename Src, template <typename> class Bounds>
-struct DstRangeRelationToSrcRangeImpl<Dst,
- Src,
- Bounds,
- INTEGER_REPRESENTATION_UNSIGNED,
- INTEGER_REPRESENTATION_SIGNED,
- NUMERIC_RANGE_NOT_CONTAINED> {
+struct DstRangeRelationToSrcRangeImpl<
+ Dst,
+ Src,
+ Bounds,
+ IntegerRepresentation::kUnsigned,
+ IntegerRepresentation::kSigned,
+ NumericRangeRepresentation::kNotContained> {
static constexpr RangeCheck Check(Src value) {
using SrcLimits = std::numeric_limits<Src>;
using DstLimits = NarrowingRange<Dst, Src, Bounds>;
using Promotion = decltype(Src() + Dst());
- bool ge_zero = false;
+ bool ge_zero;
// Converting floating-point to integer will discard fractional part, so
// values in (-1.0, -0.0) will truncate to 0 and fit in Dst.
- if (std::is_floating_point<Src>::value) {
- ge_zero = value > Src(-1);
+ if constexpr (std::is_floating_point_v<Src>) {
+ ge_zero = value > Src{-1};
} else {
- ge_zero = value >= Src(0);
+ ge_zero = value >= Src{0};
}
return RangeCheck(
ge_zero && (DstLimits::lowest() == 0 ||
@@ -388,30 +337,28 @@
// Simple wrapper for statically checking if a type's range is contained.
template <typename Dst, typename Src>
-struct IsTypeInRangeForNumericType {
- static const bool value = StaticDstRangeRelationToSrcRange<Dst, Src>::value ==
- NUMERIC_RANGE_CONTAINED;
-};
+inline constexpr bool kIsTypeInRangeForNumericType =
+ kStaticDstRangeRelationToSrcRange<Dst, Src> ==
+ NumericRangeRepresentation::kContained;
template <typename Dst,
template <typename> class Bounds = std::numeric_limits,
typename Src>
+ requires(std::is_arithmetic_v<Src> && std::is_arithmetic_v<Dst> &&
+ Bounds<Dst>::lowest() < Bounds<Dst>::max())
constexpr RangeCheck DstRangeRelationToSrcRange(Src value) {
- static_assert(std::is_arithmetic<Src>::value, "Argument must be numeric.");
- static_assert(std::is_arithmetic<Dst>::value, "Result must be numeric.");
- static_assert(Bounds<Dst>::lowest() < Bounds<Dst>::max(), "");
return DstRangeRelationToSrcRangeImpl<Dst, Src, Bounds>::Check(value);
}
// Integer promotion templates used by the portable checked integer arithmetic.
template <size_t Size, bool IsSigned>
-struct IntegerForDigitsAndSign;
+struct IntegerForDigitsAndSignImpl;
-#define INTEGER_FOR_DIGITS_AND_SIGN(I) \
- template <> \
- struct IntegerForDigitsAndSign<IntegerBitsPlusSign<I>::value, \
- std::is_signed<I>::value> { \
- using type = I; \
+#define INTEGER_FOR_DIGITS_AND_SIGN(I) \
+ template <> \
+ struct IntegerForDigitsAndSignImpl<kIntegerBitsPlusSign<I>, \
+ std::is_signed_v<I>> { \
+ using type = I; \
}
INTEGER_FOR_DIGITS_AND_SIGN(int8_t);
@@ -424,422 +371,352 @@
INTEGER_FOR_DIGITS_AND_SIGN(uint64_t);
#undef INTEGER_FOR_DIGITS_AND_SIGN
+template <size_t Size, bool IsSigned>
+using IntegerForDigitsAndSign =
+ IntegerForDigitsAndSignImpl<Size, IsSigned>::type;
+
// WARNING: We have no IntegerForSizeAndSign<16, *>. If we ever add one to
// support 128-bit math, then the ArithmeticPromotion template below will need
// to be updated (or more likely replaced with a decltype expression).
-static_assert(IntegerBitsPlusSign<intmax_t>::value == 64,
+static_assert(kIntegerBitsPlusSign<intmax_t> == 64,
"Max integer size not supported for this toolchain.");
-template <typename Integer, bool IsSigned = std::is_signed<Integer>::value>
-struct TwiceWiderInteger {
- using type =
- typename IntegerForDigitsAndSign<IntegerBitsPlusSign<Integer>::value * 2,
- IsSigned>::type;
-};
-
-enum ArithmeticPromotionCategory {
- LEFT_PROMOTION, // Use the type of the left-hand argument.
- RIGHT_PROMOTION // Use the type of the right-hand argument.
-};
+template <typename Integer, bool IsSigned = std::is_signed_v<Integer>>
+using TwiceWiderInteger =
+ IntegerForDigitsAndSign<kIntegerBitsPlusSign<Integer> * 2, IsSigned>;
// Determines the type that can represent the largest positive value.
-template <typename Lhs,
- typename Rhs,
- ArithmeticPromotionCategory Promotion =
- (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value)
- ? LEFT_PROMOTION
- : RIGHT_PROMOTION>
-struct MaxExponentPromotion;
-
template <typename Lhs, typename Rhs>
-struct MaxExponentPromotion<Lhs, Rhs, LEFT_PROMOTION> {
- using type = Lhs;
-};
-
-template <typename Lhs, typename Rhs>
-struct MaxExponentPromotion<Lhs, Rhs, RIGHT_PROMOTION> {
- using type = Rhs;
-};
+using MaxExponentPromotion =
+ std::conditional_t<(kMaxExponent<Lhs> > kMaxExponent<Rhs>), Lhs, Rhs>;
// Determines the type that can represent the lowest arithmetic value.
-template <typename Lhs,
- typename Rhs,
- ArithmeticPromotionCategory Promotion =
- std::is_signed<Lhs>::value
- ? (std::is_signed<Rhs>::value
- ? (MaxExponent<Lhs>::value > MaxExponent<Rhs>::value
- ? LEFT_PROMOTION
- : RIGHT_PROMOTION)
- : LEFT_PROMOTION)
- : (std::is_signed<Rhs>::value
- ? RIGHT_PROMOTION
- : (MaxExponent<Lhs>::value < MaxExponent<Rhs>::value
- ? LEFT_PROMOTION
- : RIGHT_PROMOTION))>
-struct LowestValuePromotion;
-
template <typename Lhs, typename Rhs>
-struct LowestValuePromotion<Lhs, Rhs, LEFT_PROMOTION> {
- using type = Lhs;
-};
-
-template <typename Lhs, typename Rhs>
-struct LowestValuePromotion<Lhs, Rhs, RIGHT_PROMOTION> {
- using type = Rhs;
-};
+using LowestValuePromotion = std::conditional_t<
+ std::is_signed_v<Lhs>
+ ? (!std::is_signed_v<Rhs> || kMaxExponent<Lhs> > kMaxExponent<Rhs>)
+ : (!std::is_signed_v<Rhs> && kMaxExponent<Lhs> < kMaxExponent<Rhs>),
+ Lhs,
+ Rhs>;
// Determines the type that is best able to represent an arithmetic result.
-template <
- typename Lhs,
- typename Rhs = Lhs,
- bool is_intmax_type =
- std::is_integral<
- typename MaxExponentPromotion<Lhs, Rhs>::type>::value &&
- IntegerBitsPlusSign<typename MaxExponentPromotion<Lhs, Rhs>::type>::
- value == IntegerBitsPlusSign<intmax_t>::value,
- bool is_max_exponent = StaticDstRangeRelationToSrcRange<
- typename MaxExponentPromotion<Lhs, Rhs>::type,
- Lhs>::value == NUMERIC_RANGE_CONTAINED &&
- StaticDstRangeRelationToSrcRange<
- typename MaxExponentPromotion<Lhs, Rhs>::type,
- Rhs>::value == NUMERIC_RANGE_CONTAINED>
-struct BigEnoughPromotion;
-// The side with the max exponent is big enough.
-template <typename Lhs, typename Rhs, bool is_intmax_type>
-struct BigEnoughPromotion<Lhs, Rhs, is_intmax_type, true> {
- using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
- static const bool is_contained = true;
+// Default case, used when the side with the max exponent is big enough.
+template <typename Lhs,
+ typename Rhs = Lhs,
+ bool is_intmax_type =
+ std::is_integral_v<MaxExponentPromotion<Lhs, Rhs>> &&
+ kIntegerBitsPlusSign<MaxExponentPromotion<Lhs, Rhs>> ==
+ kIntegerBitsPlusSign<intmax_t>,
+ bool is_max_exponent =
+ kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>,
+ Lhs> ==
+ NumericRangeRepresentation::kContained &&
+ kStaticDstRangeRelationToSrcRange<MaxExponentPromotion<Lhs, Rhs>,
+ Rhs> ==
+ NumericRangeRepresentation::kContained>
+struct BigEnoughPromotionImpl {
+ using type = MaxExponentPromotion<Lhs, Rhs>;
+ static constexpr bool kContained = true;
};
// We can use a twice wider type to fit.
template <typename Lhs, typename Rhs>
-struct BigEnoughPromotion<Lhs, Rhs, false, false> {
+struct BigEnoughPromotionImpl<Lhs, Rhs, false, false> {
using type =
- typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
- std::is_signed<Lhs>::value ||
- std::is_signed<Rhs>::value>::type;
- static const bool is_contained = true;
+ TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>,
+ std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>;
+ static constexpr bool kContained = true;
};
// No type is large enough.
template <typename Lhs, typename Rhs>
-struct BigEnoughPromotion<Lhs, Rhs, true, false> {
- using type = typename MaxExponentPromotion<Lhs, Rhs>::type;
- static const bool is_contained = false;
+struct BigEnoughPromotionImpl<Lhs, Rhs, true, false> {
+ using type = MaxExponentPromotion<Lhs, Rhs>;
+ static constexpr bool kContained = false;
};
+template <typename Lhs, typename Rhs>
+using BigEnoughPromotion = BigEnoughPromotionImpl<Lhs, Rhs>::type;
+
+template <typename Lhs, typename Rhs>
+inline constexpr bool kIsBigEnoughPromotionContained =
+ BigEnoughPromotionImpl<Lhs, Rhs>::kContained;
+
// We can statically check if operations on the provided types can wrap, so we
// can skip the checked operations if they're not needed. So, for an integer we
// care if the destination type preserves the sign and is twice the width of
// the source.
template <typename T, typename Lhs, typename Rhs = Lhs>
-struct IsIntegerArithmeticSafe {
- static const bool value =
- !std::is_floating_point<T>::value &&
- !std::is_floating_point<Lhs>::value &&
- !std::is_floating_point<Rhs>::value &&
- std::is_signed<T>::value >= std::is_signed<Lhs>::value &&
- IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Lhs>::value) &&
- std::is_signed<T>::value >= std::is_signed<Rhs>::value &&
- IntegerBitsPlusSign<T>::value >= (2 * IntegerBitsPlusSign<Rhs>::value);
-};
+inline constexpr bool kIsIntegerArithmeticSafe =
+ !std::is_floating_point_v<T> && !std::is_floating_point_v<Lhs> &&
+ !std::is_floating_point_v<Rhs> &&
+ std::is_signed_v<T> >= std::is_signed_v<Lhs> &&
+ kIntegerBitsPlusSign<T> >= (2 * kIntegerBitsPlusSign<Lhs>) &&
+ std::is_signed_v<T> >= std::is_signed_v<Rhs> &&
+ kIntegerBitsPlusSign<T> >= (2 * kIntegerBitsPlusSign<Rhs>);
// Promotes to a type that can represent any possible result of a binary
// arithmetic operation with the source types.
-template <typename Lhs,
- typename Rhs,
- bool is_promotion_possible = IsIntegerArithmeticSafe<
- typename std::conditional<std::is_signed<Lhs>::value ||
- std::is_signed<Rhs>::value,
- intmax_t,
- uintmax_t>::type,
- typename MaxExponentPromotion<Lhs, Rhs>::type>::value>
-struct FastIntegerArithmeticPromotion;
-
template <typename Lhs, typename Rhs>
-struct FastIntegerArithmeticPromotion<Lhs, Rhs, true> {
- using type =
- typename TwiceWiderInteger<typename MaxExponentPromotion<Lhs, Rhs>::type,
- std::is_signed<Lhs>::value ||
- std::is_signed<Rhs>::value>::type;
- static_assert(IsIntegerArithmeticSafe<type, Lhs, Rhs>::value, "");
- static const bool is_contained = true;
+struct FastIntegerArithmeticPromotionImpl {
+ using type = BigEnoughPromotion<Lhs, Rhs>;
+ static constexpr bool kContained = false;
};
template <typename Lhs, typename Rhs>
-struct FastIntegerArithmeticPromotion<Lhs, Rhs, false> {
- using type = typename BigEnoughPromotion<Lhs, Rhs>::type;
- static const bool is_contained = false;
+ requires(kIsIntegerArithmeticSafe<
+ std::conditional_t<std::is_signed_v<Lhs> || std::is_signed_v<Rhs>,
+ intmax_t,
+ uintmax_t>,
+ MaxExponentPromotion<Lhs, Rhs>>)
+struct FastIntegerArithmeticPromotionImpl<Lhs, Rhs> {
+ using type =
+ TwiceWiderInteger<MaxExponentPromotion<Lhs, Rhs>,
+ std::is_signed_v<Lhs> || std::is_signed_v<Rhs>>;
+ static_assert(kIsIntegerArithmeticSafe<type, Lhs, Rhs>);
+ static constexpr bool kContained = true;
+};
+
+template <typename Lhs, typename Rhs>
+using FastIntegerArithmeticPromotion =
+ FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::type;
+
+template <typename Lhs, typename Rhs>
+inline constexpr bool kIsFastIntegerArithmeticPromotionContained =
+ FastIntegerArithmeticPromotionImpl<Lhs, Rhs>::kContained;
+
+template <typename T>
+struct ArithmeticOrIntegralConstant {
+ using type = T;
+};
+
+template <typename T>
+ requires IntegralConstantLike<T>
+struct ArithmeticOrIntegralConstant<T> {
+ using type = T::value_type;
};
// Extracts the underlying type from an enum.
-template <typename T, bool is_enum = std::is_enum<T>::value>
-struct ArithmeticOrUnderlyingEnum;
-
template <typename T>
-struct ArithmeticOrUnderlyingEnum<T, true> {
- using type = typename std::underlying_type<T>::type;
- static const bool value = std::is_arithmetic<type>::value;
-};
-
-template <typename T>
-struct ArithmeticOrUnderlyingEnum<T, false> {
- using type = T;
- static const bool value = std::is_arithmetic<type>::value;
-};
+using ArithmeticOrUnderlyingEnum =
+ typename std::conditional_t<std::is_enum_v<T>,
+ std::underlying_type<T>,
+ ArithmeticOrIntegralConstant<T>>::type;
// The following are helper templates used in the CheckedNumeric class.
template <typename T>
+ requires std::is_arithmetic_v<T>
class CheckedNumeric;
template <typename T>
+ requires std::is_arithmetic_v<T>
class ClampedNumeric;
template <typename T>
+ requires std::is_arithmetic_v<T>
class StrictNumeric;
// Used to treat CheckedNumeric and arithmetic underlying types the same.
template <typename T>
-struct UnderlyingType {
- using type = typename ArithmeticOrUnderlyingEnum<T>::type;
- static const bool is_numeric = std::is_arithmetic<type>::value;
- static const bool is_checked = false;
- static const bool is_clamped = false;
- static const bool is_strict = false;
-};
+inline constexpr bool kIsCheckedNumeric = false;
+template <typename T>
+inline constexpr bool kIsCheckedNumeric<CheckedNumeric<T>> = true;
+template <typename T>
+concept IsCheckedNumeric = kIsCheckedNumeric<T>;
template <typename T>
-struct UnderlyingType<CheckedNumeric<T>> {
- using type = T;
- static const bool is_numeric = true;
- static const bool is_checked = true;
- static const bool is_clamped = false;
- static const bool is_strict = false;
-};
+inline constexpr bool kIsClampedNumeric = false;
+template <typename T>
+inline constexpr bool kIsClampedNumeric<ClampedNumeric<T>> = true;
+template <typename T>
+concept IsClampedNumeric = kIsClampedNumeric<T>;
template <typename T>
-struct UnderlyingType<ClampedNumeric<T>> {
- using type = T;
- static const bool is_numeric = true;
- static const bool is_checked = false;
- static const bool is_clamped = true;
- static const bool is_strict = false;
-};
+inline constexpr bool kIsStrictNumeric = false;
+template <typename T>
+inline constexpr bool kIsStrictNumeric<StrictNumeric<T>> = true;
+template <typename T>
+concept IsStrictNumeric = kIsStrictNumeric<T>;
template <typename T>
-struct UnderlyingType<StrictNumeric<T>> {
+struct UnderlyingTypeImpl {
+ using type = ArithmeticOrUnderlyingEnum<T>;
+};
+template <typename T>
+struct UnderlyingTypeImpl<CheckedNumeric<T>> {
using type = T;
- static const bool is_numeric = true;
- static const bool is_checked = false;
- static const bool is_clamped = false;
- static const bool is_strict = true;
};
+template <typename T>
+struct UnderlyingTypeImpl<ClampedNumeric<T>> {
+ using type = T;
+};
+template <typename T>
+struct UnderlyingTypeImpl<StrictNumeric<T>> {
+ using type = T;
+};
+template <typename T>
+using UnderlyingType = UnderlyingTypeImpl<T>::type;
+
+template <typename T>
+inline constexpr bool kIsNumeric = std::is_arithmetic_v<UnderlyingType<T>>;
+template <typename T>
+ requires(IsCheckedNumeric<T> || IsClampedNumeric<T> || IsStrictNumeric<T>)
+inline constexpr bool kIsNumeric<T> = true;
+template <typename T>
+concept IsNumeric = kIsNumeric<T>;
template <typename L, typename R>
-struct IsCheckedOp {
- static const bool value =
- UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
- (UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
-};
+concept IsCheckedOp = (IsCheckedNumeric<L> && IsNumeric<R>) ||
+ (IsCheckedNumeric<R> && IsNumeric<L>);
template <typename L, typename R>
-struct IsClampedOp {
- static const bool value =
- UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
- (UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped) &&
- !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked);
-};
+concept IsClampedOp =
+ !IsCheckedOp<L, R> && ((IsClampedNumeric<L> && IsNumeric<R>) ||
+ (IsClampedNumeric<R> && IsNumeric<L>));
template <typename L, typename R>
-struct IsStrictOp {
- static const bool value =
- UnderlyingType<L>::is_numeric && UnderlyingType<R>::is_numeric &&
- (UnderlyingType<L>::is_strict || UnderlyingType<R>::is_strict) &&
- !(UnderlyingType<L>::is_checked || UnderlyingType<R>::is_checked) &&
- !(UnderlyingType<L>::is_clamped || UnderlyingType<R>::is_clamped);
-};
+concept IsStrictOp = !IsCheckedOp<L, R> && !IsClampedOp<L, R> &&
+ ((IsStrictNumeric<L> && IsNumeric<R>) ||
+ (IsStrictNumeric<R> && IsNumeric<L>));
// as_signed<> returns the supplied integral value (or integral castable
// Numeric template) cast as a signed integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_signed<T>::type>(t)
-template <typename Src>
-constexpr typename std::make_signed<
- typename internal::UnderlyingType<Src>::type>::type
-as_signed(const Src value) {
- static_assert(std::is_integral<decltype(as_signed(value))>::value,
- "Argument must be a signed or unsigned integer type.");
- return static_cast<decltype(as_signed(value))>(value);
+template <typename Src, typename Dst = std::make_signed_t<UnderlyingType<Src>>>
+ requires std::integral<Dst>
+constexpr auto as_signed(Src value) {
+ return static_cast<Dst>(value);
}
// as_unsigned<> returns the supplied integral value (or integral castable
// Numeric template) cast as an unsigned integral of equivalent precision.
// I.e. it's mostly an alias for: static_cast<std::make_unsigned<T>::type>(t)
-template <typename Src>
-constexpr typename std::make_unsigned<
- typename internal::UnderlyingType<Src>::type>::type
-as_unsigned(const Src value) {
- static_assert(std::is_integral<decltype(as_unsigned(value))>::value,
- "Argument must be a signed or unsigned integer type.");
- return static_cast<decltype(as_unsigned(value))>(value);
+template <typename Src,
+ typename Dst = std::make_unsigned_t<UnderlyingType<Src>>>
+ requires std::integral<Dst>
+constexpr auto as_unsigned(Src value) {
+ return static_cast<Dst>(value);
}
template <typename L, typename R>
-constexpr bool IsLessImpl(const L lhs,
- const R rhs,
- const RangeCheck l_range,
- const RangeCheck r_range) {
- return l_range.IsUnderflow() || r_range.IsOverflow() ||
- (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <
- static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
struct IsLess {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- static constexpr bool Test(const L lhs, const R rhs) {
- return IsLessImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
- DstRangeRelationToSrcRange<L>(rhs));
+ using SumT = decltype(std::declval<L>() + std::declval<R>());
+ static constexpr bool Test(L lhs, R rhs) {
+ const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+ const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+ return l_range.IsUnderflow() || r_range.IsOverflow() ||
+ (l_range == r_range &&
+ static_cast<SumT>(lhs) < static_cast<SumT>(rhs));
}
};
template <typename L, typename R>
-constexpr bool IsLessOrEqualImpl(const L lhs,
- const R rhs,
- const RangeCheck l_range,
- const RangeCheck r_range) {
- return l_range.IsUnderflow() || r_range.IsOverflow() ||
- (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) <=
- static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
struct IsLessOrEqual {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- static constexpr bool Test(const L lhs, const R rhs) {
- return IsLessOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
- DstRangeRelationToSrcRange<L>(rhs));
+ using SumT = decltype(std::declval<L>() + std::declval<R>());
+ static constexpr bool Test(L lhs, R rhs) {
+ const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+ const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+ return l_range.IsUnderflow() || r_range.IsOverflow() ||
+ (l_range == r_range &&
+ static_cast<SumT>(lhs) <= static_cast<SumT>(rhs));
}
};
template <typename L, typename R>
-constexpr bool IsGreaterImpl(const L lhs,
- const R rhs,
- const RangeCheck l_range,
- const RangeCheck r_range) {
- return l_range.IsOverflow() || r_range.IsUnderflow() ||
- (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >
- static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
struct IsGreater {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- static constexpr bool Test(const L lhs, const R rhs) {
- return IsGreaterImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
- DstRangeRelationToSrcRange<L>(rhs));
+ using SumT = decltype(std::declval<L>() + std::declval<R>());
+ static constexpr bool Test(L lhs, R rhs) {
+ const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+ const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+ return l_range.IsOverflow() || r_range.IsUnderflow() ||
+ (l_range == r_range &&
+ static_cast<SumT>(lhs) > static_cast<SumT>(rhs));
}
};
template <typename L, typename R>
-constexpr bool IsGreaterOrEqualImpl(const L lhs,
- const R rhs,
- const RangeCheck l_range,
- const RangeCheck r_range) {
- return l_range.IsOverflow() || r_range.IsUnderflow() ||
- (l_range == r_range && static_cast<decltype(lhs + rhs)>(lhs) >=
- static_cast<decltype(lhs + rhs)>(rhs));
-}
-
-template <typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
struct IsGreaterOrEqual {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- static constexpr bool Test(const L lhs, const R rhs) {
- return IsGreaterOrEqualImpl(lhs, rhs, DstRangeRelationToSrcRange<R>(lhs),
- DstRangeRelationToSrcRange<L>(rhs));
+ using SumT = decltype(std::declval<L>() + std::declval<R>());
+ static constexpr bool Test(L lhs, R rhs) {
+ const RangeCheck l_range = DstRangeRelationToSrcRange<R>(lhs);
+ const RangeCheck r_range = DstRangeRelationToSrcRange<L>(rhs);
+ return l_range.IsOverflow() || r_range.IsUnderflow() ||
+ (l_range == r_range &&
+ static_cast<SumT>(lhs) >= static_cast<SumT>(rhs));
}
};
template <typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
struct IsEqual {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- static constexpr bool Test(const L lhs, const R rhs) {
+ using SumT = decltype(std::declval<L>() + std::declval<R>());
+ static constexpr bool Test(L lhs, R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) ==
DstRangeRelationToSrcRange<L>(rhs) &&
- static_cast<decltype(lhs + rhs)>(lhs) ==
- static_cast<decltype(lhs + rhs)>(rhs);
+ static_cast<SumT>(lhs) == static_cast<SumT>(rhs);
}
};
template <typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
struct IsNotEqual {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- static constexpr bool Test(const L lhs, const R rhs) {
+ using SumT = decltype(std::declval<L>() + std::declval<R>());
+ static constexpr bool Test(L lhs, R rhs) {
return DstRangeRelationToSrcRange<R>(lhs) !=
DstRangeRelationToSrcRange<L>(rhs) ||
- static_cast<decltype(lhs + rhs)>(lhs) !=
- static_cast<decltype(lhs + rhs)>(rhs);
+ static_cast<SumT>(lhs) != static_cast<SumT>(rhs);
}
};
// These perform the actual math operations on the CheckedNumerics.
// Binary arithmetic operations.
-template <template <typename, typename> class C, typename L, typename R>
-constexpr bool SafeCompare(const L lhs, const R rhs) {
- static_assert(std::is_arithmetic<L>::value && std::is_arithmetic<R>::value,
- "Types must be numeric.");
- using Promotion = BigEnoughPromotion<L, R>;
- using BigType = typename Promotion::type;
- return Promotion::is_contained
+template <template <typename, typename> typename C, typename L, typename R>
+ requires std::is_arithmetic_v<L> && std::is_arithmetic_v<R>
+constexpr bool SafeCompare(L lhs, R rhs) {
+ using BigType = BigEnoughPromotion<L, R>;
+ return kIsBigEnoughPromotionContained<L, R>
// Force to a larger type for speed if both are contained.
- ? C<BigType, BigType>::Test(
- static_cast<BigType>(static_cast<L>(lhs)),
- static_cast<BigType>(static_cast<R>(rhs)))
+ ? C<BigType, BigType>::Test(static_cast<BigType>(lhs),
+ static_cast<BigType>(rhs))
// Let the template functions figure it out for mixed types.
: C<L, R>::Test(lhs, rhs);
}
template <typename Dst, typename Src>
-constexpr bool IsMaxInRangeForNumericType() {
- return IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
- std::numeric_limits<Src>::max());
-}
+inline constexpr bool kIsMaxInRangeForNumericType =
+ IsGreaterOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::max(),
+ std::numeric_limits<Src>::max());
template <typename Dst, typename Src>
-constexpr bool IsMinInRangeForNumericType() {
- return IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
- std::numeric_limits<Src>::lowest());
-}
+inline constexpr bool kIsMinInRangeForNumericType =
+ IsLessOrEqual<Dst, Src>::Test(std::numeric_limits<Dst>::lowest(),
+ std::numeric_limits<Src>::lowest());
template <typename Dst, typename Src>
-constexpr Dst CommonMax() {
- return !IsMaxInRangeForNumericType<Dst, Src>()
- ? Dst(std::numeric_limits<Dst>::max())
- : Dst(std::numeric_limits<Src>::max());
-}
+inline constexpr Dst kCommonMax =
+ kIsMaxInRangeForNumericType<Dst, Src>
+ ? static_cast<Dst>(std::numeric_limits<Src>::max())
+ : std::numeric_limits<Dst>::max();
template <typename Dst, typename Src>
-constexpr Dst CommonMin() {
- return !IsMinInRangeForNumericType<Dst, Src>()
- ? Dst(std::numeric_limits<Dst>::lowest())
- : Dst(std::numeric_limits<Src>::lowest());
-}
+inline constexpr Dst kCommonMin =
+ kIsMinInRangeForNumericType<Dst, Src>
+ ? static_cast<Dst>(std::numeric_limits<Src>::lowest())
+ : std::numeric_limits<Dst>::lowest();
// This is a wrapper to generate return the max or min for a supplied type.
// If the argument is false, the returned value is the maximum. If true the
// returned value is the minimum.
template <typename Dst, typename Src = Dst>
constexpr Dst CommonMaxOrMin(bool is_min) {
- return is_min ? CommonMin<Dst, Src>() : CommonMax<Dst, Src>();
+ return is_min ? kCommonMin<Dst, Src> : kCommonMax<Dst, Src>;
}
-} // namespace internal
-} // namespace pdfium
+} // namespace pdfium::internal
#endif // CORE_FXCRT_NUMERICS_SAFE_CONVERSIONS_IMPL_H_
diff --git a/core/fxcrt/numerics/safe_math.h b/core/fxcrt/numerics/safe_math.h
index f910027..c47b79b 100644
--- a/core/fxcrt/numerics/safe_math.h
+++ b/core/fxcrt/numerics/safe_math.h
@@ -5,8 +5,8 @@
#ifndef CORE_FXCRT_NUMERICS_SAFE_MATH_H_
#define CORE_FXCRT_NUMERICS_SAFE_MATH_H_
-#include "core/fxcrt/numerics/checked_math.h"
-#include "core/fxcrt/numerics/clamped_math.h"
-#include "core/fxcrt/numerics/safe_conversions.h"
+#include "core/fxcrt/numerics/checked_math.h" // IWYU pragma: export
+#include "core/fxcrt/numerics/clamped_math.h" // IWYU pragma: export
+#include "core/fxcrt/numerics/safe_conversions.h" // IWYU pragma: export
#endif // CORE_FXCRT_NUMERICS_SAFE_MATH_H_
diff --git a/core/fxcrt/numerics/safe_math_arm_impl.h b/core/fxcrt/numerics/safe_math_arm_impl.h
index f2d83cb..00a38c6 100644
--- a/core/fxcrt/numerics/safe_math_arm_impl.h
+++ b/core/fxcrt/numerics/safe_math_arm_impl.h
@@ -5,18 +5,20 @@
#ifndef CORE_FXCRT_NUMERICS_SAFE_MATH_ARM_IMPL_H_
#define CORE_FXCRT_NUMERICS_SAFE_MATH_ARM_IMPL_H_
+// IWYU pragma: private
+
+#include <stdint.h>
+
#include <cassert>
-#include <type_traits>
#include "core/fxcrt/numerics/safe_conversions.h"
-namespace pdfium {
-namespace internal {
+namespace pdfium::internal {
template <typename T, typename U>
struct CheckedMulFastAsmOp {
- static const bool is_supported =
- kEnableAsmCode && FastIntegerArithmeticPromotion<T, U>::is_contained;
+ static constexpr bool is_supported =
+ kEnableAsmCode && kIsFastIntegerArithmeticPromotionContained<T, U>;
// The following is not an assembler routine and is thus constexpr safe, it
// just emits much more efficient code than the Clang and GCC builtins for
@@ -32,7 +34,7 @@
// cmp r2, r1, asr #15
template <typename V>
static constexpr bool Do(T x, U y, V* result) {
- using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
+ using Promotion = FastIntegerArithmeticPromotion<T, U>;
Promotion presult;
presult = static_cast<Promotion>(x) * static_cast<Promotion>(y);
@@ -46,83 +48,78 @@
template <typename T, typename U>
struct ClampedAddFastAsmOp {
- static const bool is_supported =
- kEnableAsmCode && BigEnoughPromotion<T, U>::is_contained &&
- IsTypeInRangeForNumericType<
- int32_t,
- typename BigEnoughPromotion<T, U>::type>::value;
+ static constexpr bool is_supported =
+ kEnableAsmCode && kIsBigEnoughPromotionContained<T, U> &&
+ kIsTypeInRangeForNumericType<int32_t, BigEnoughPromotion<T, U>>;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
- if (IsIntegerArithmeticSafe<int, T, U>::value) {
+ if constexpr (kIsIntegerArithmeticSafe<int, T, U>) {
return saturated_cast<V>(static_cast<int>(x) + static_cast<int>(y));
+ } else {
+ int32_t result;
+ int32_t x_i32 = checked_cast<int32_t>(x);
+ int32_t y_i32 = checked_cast<int32_t>(y);
+
+ asm("qadd %[result], %[first], %[second]"
+ : [result] "=r"(result)
+ : [first] "r"(x_i32), [second] "r"(y_i32));
+ return saturated_cast<V>(result);
}
-
- int32_t result;
- int32_t x_i32 = checked_cast<int32_t>(x);
- int32_t y_i32 = checked_cast<int32_t>(y);
-
- asm("qadd %[result], %[first], %[second]"
- : [result] "=r"(result)
- : [first] "r"(x_i32), [second] "r"(y_i32));
- return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedSubFastAsmOp {
- static const bool is_supported =
- kEnableAsmCode && BigEnoughPromotion<T, U>::is_contained &&
- IsTypeInRangeForNumericType<
- int32_t,
- typename BigEnoughPromotion<T, U>::type>::value;
+ static constexpr bool is_supported =
+ kEnableAsmCode && kIsBigEnoughPromotionContained<T, U> &&
+ kIsTypeInRangeForNumericType<int32_t, BigEnoughPromotion<T, U>>;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// This will get promoted to an int, so let the compiler do whatever is
// clever and rely on the saturated cast to bounds check.
- if (IsIntegerArithmeticSafe<int, T, U>::value) {
+ if constexpr (kIsIntegerArithmeticSafe<int, T, U>) {
return saturated_cast<V>(static_cast<int>(x) - static_cast<int>(y));
+ } else {
+ int32_t result;
+ int32_t x_i32 = checked_cast<int32_t>(x);
+ int32_t y_i32 = checked_cast<int32_t>(y);
+
+ asm("qsub %[result], %[first], %[second]"
+ : [result] "=r"(result)
+ : [first] "r"(x_i32), [second] "r"(y_i32));
+ return saturated_cast<V>(result);
}
-
- int32_t result;
- int32_t x_i32 = checked_cast<int32_t>(x);
- int32_t y_i32 = checked_cast<int32_t>(y);
-
- asm("qsub %[result], %[first], %[second]"
- : [result] "=r"(result)
- : [first] "r"(x_i32), [second] "r"(y_i32));
- return saturated_cast<V>(result);
}
};
template <typename T, typename U>
struct ClampedMulFastAsmOp {
- static const bool is_supported =
+ static constexpr bool is_supported =
kEnableAsmCode && CheckedMulFastAsmOp<T, U>::is_supported;
template <typename V>
__attribute__((always_inline)) static V Do(T x, U y) {
// Use the CheckedMulFastAsmOp for full-width 32-bit values, because
// it's fewer instructions than promoting and then saturating.
- if (!IsIntegerArithmeticSafe<int32_t, T, U>::value &&
- !IsIntegerArithmeticSafe<uint32_t, T, U>::value) {
+ if constexpr (!kIsIntegerArithmeticSafe<int32_t, T, U> &&
+ !kIsIntegerArithmeticSafe<uint32_t, T, U>) {
V result;
return CheckedMulFastAsmOp<T, U>::Do(x, y, &result)
? result
: CommonMaxOrMin<V>(IsValueNegative(x) ^ IsValueNegative(y));
+ } else {
+ static_assert(kIsFastIntegerArithmeticPromotionContained<T, U>);
+ using Promotion = FastIntegerArithmeticPromotion<T, U>;
+ return saturated_cast<V>(static_cast<Promotion>(x) *
+ static_cast<Promotion>(y));
}
-
- assert((FastIntegerArithmeticPromotion<T, U>::is_contained));
- using Promotion = typename FastIntegerArithmeticPromotion<T, U>::type;
- return saturated_cast<V>(static_cast<Promotion>(x) *
- static_cast<Promotion>(y));
}
};
-} // namespace internal
-} // namespace pdfium
+} // namespace pdfium::internal
#endif // CORE_FXCRT_NUMERICS_SAFE_MATH_ARM_IMPL_H_
diff --git a/core/fxcrt/numerics/safe_math_clang_gcc_impl.h b/core/fxcrt/numerics/safe_math_clang_gcc_impl.h
index ca6b16a..ca21d40 100644
--- a/core/fxcrt/numerics/safe_math_clang_gcc_impl.h
+++ b/core/fxcrt/numerics/safe_math_clang_gcc_impl.h
@@ -5,14 +5,17 @@
#ifndef CORE_FXCRT_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
#define CORE_FXCRT_NUMERICS_SAFE_MATH_CLANG_GCC_IMPL_H_
-#include <cassert>
+// IWYU pragma: private
+
+#include <stdint.h>
+
#include <limits>
#include <type_traits>
#include "core/fxcrt/numerics/safe_conversions.h"
#if !defined(__native_client__) && (defined(__ARMEL__) || defined(__arch64__))
-#include "core/fxcrt/numerics/safe_math_arm_impl.h"
+#include "core/fxcrt/numerics/safe_math_arm_impl.h" // IWYU pragma: export
#define BASE_HAS_ASSEMBLER_SAFE_MATH (1)
#else
#define BASE_HAS_ASSEMBLER_SAFE_MATH (0)
@@ -92,10 +95,10 @@
// https://crbug.com/613003
// We can support intptr_t, uintptr_t, or a smaller common type.
static const bool is_supported =
- (IsTypeInRangeForNumericType<intptr_t, T>::value &&
- IsTypeInRangeForNumericType<intptr_t, U>::value) ||
- (IsTypeInRangeForNumericType<uintptr_t, T>::value &&
- IsTypeInRangeForNumericType<uintptr_t, U>::value);
+ (kIsTypeInRangeForNumericType<intptr_t, T> &&
+ kIsTypeInRangeForNumericType<intptr_t, U>) ||
+ (kIsTypeInRangeForNumericType<uintptr_t, T> &&
+ kIsTypeInRangeForNumericType<uintptr_t, U>);
#else
static const bool is_supported = true;
#endif
@@ -136,7 +139,7 @@
template <typename T>
struct ClampedNegFastOp {
- static const bool is_supported = std::is_signed<T>::value;
+ static const bool is_supported = std::is_signed_v<T>;
__attribute__((always_inline)) static T Do(T value) {
// Use this when there is no assembler path available.
if (!ClampedSubFastAsmOp<T, T>::is_supported) {
diff --git a/core/fxcrt/numerics/safe_math_shared_impl.h b/core/fxcrt/numerics/safe_math_shared_impl.h
index 168d5f3..ffdef70 100644
--- a/core/fxcrt/numerics/safe_math_shared_impl.h
+++ b/core/fxcrt/numerics/safe_math_shared_impl.h
@@ -5,20 +5,15 @@
#ifndef CORE_FXCRT_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
#define CORE_FXCRT_NUMERICS_SAFE_MATH_SHARED_IMPL_H_
-#include <stddef.h>
-#include <stdint.h>
+// IWYU pragma: private
-#include <cassert>
-#include <climits>
-#include <cmath>
-#include <cstdlib>
-#include <limits>
+#include <concepts>
#include <type_traits>
#include "build/build_config.h"
#include "core/fxcrt/numerics/safe_conversions.h"
-#if BUILDFLAG(IS_ASMJS)
+#if defined(__asmjs__) || defined(__wasm__)
// Optimized safe math instructions are incompatible with asmjs.
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
// Where available use builtin math overflow support on Clang and GCC.
@@ -27,7 +22,7 @@
((__clang_major__ > 3) || \
(__clang_major__ == 3 && __clang_minor__ >= 4))) || \
(defined(__GNUC__) && __GNUC__ >= 5))
-#include "core/fxcrt/numerics/safe_math_clang_gcc_impl.h"
+#include "core/fxcrt/numerics/safe_math_clang_gcc_impl.h" // IWYU pragma: export
#define BASE_HAS_OPTIMIZED_SAFE_MATH (1)
#else
#define BASE_HAS_OPTIMIZED_SAFE_MATH (0)
@@ -114,18 +109,18 @@
// template instantiations even though we don't actually support the operations.
// However, there is no corresponding implementation of e.g. SafeUnsignedAbs,
// so the float versions will not compile.
-template <typename Numeric,
- bool IsInteger = std::is_integral<Numeric>::value,
- bool IsFloat = std::is_floating_point<Numeric>::value>
+template <typename Numeric>
struct UnsignedOrFloatForSize;
template <typename Numeric>
-struct UnsignedOrFloatForSize<Numeric, true, false> {
+ requires(std::integral<Numeric>)
+struct UnsignedOrFloatForSize<Numeric> {
using type = typename std::make_unsigned<Numeric>::type;
};
template <typename Numeric>
-struct UnsignedOrFloatForSize<Numeric, false, true> {
+ requires(std::floating_point<Numeric>)
+struct UnsignedOrFloatForSize<Numeric> {
using type = Numeric;
};
@@ -134,47 +129,45 @@
// exhibit well-defined overflow semantics and rely on the caller to detect
// if an overflow occurred.
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::integral<T>)
constexpr T NegateWrapper(T value) {
using UnsignedT = typename std::make_unsigned<T>::type;
// This will compile to a NEG on Intel, and is normal negation on ARM.
return static_cast<T>(UnsignedT(0) - static_cast<UnsignedT>(value));
}
-template <
- typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::floating_point<T>)
constexpr T NegateWrapper(T value) {
return -value;
}
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::integral<T>)
constexpr typename std::make_unsigned<T>::type InvertWrapper(T value) {
return ~value;
}
-template <typename T,
- typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::integral<T>)
constexpr T AbsWrapper(T value) {
return static_cast<T>(SafeUnsignedAbs(value));
}
-template <
- typename T,
- typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
+template <typename T>
+ requires(std::floating_point<T>)
constexpr T AbsWrapper(T value) {
return value < 0 ? -value : value;
}
-template <template <typename, typename, typename> class M,
+template <template <typename, typename> class M,
typename L,
- typename R>
+ typename R,
+ typename Math = M<UnderlyingType<L>, UnderlyingType<R>>>
+ requires requires { typename Math::result_type; }
struct MathWrapper {
- using math = M<typename UnderlyingType<L>::type,
- typename UnderlyingType<R>::type,
- void>;
+ using math = Math;
using type = typename math::result_type;
};
@@ -183,28 +176,26 @@
// solution, but it beats rewriting these over and over again.
#define BASE_NUMERIC_ARITHMETIC_VARIADIC(CLASS, CL_ABBR, OP_NAME) \
template <typename L, typename R, typename... Args> \
- constexpr auto CL_ABBR##OP_NAME(const L lhs, const R rhs, \
- const Args... args) { \
+ constexpr auto CL_ABBR##OP_NAME(L lhs, R rhs, Args... args) { \
return CL_ABBR##MathOp<CLASS##OP_NAME##Op, L, R, Args...>(lhs, rhs, \
args...); \
}
#define BASE_NUMERIC_ARITHMETIC_OPERATORS(CLASS, CL_ABBR, OP_NAME, OP, CMP_OP) \
/* Binary arithmetic operator for all CLASS##Numeric operations. */ \
- template <typename L, typename R, \
- typename std::enable_if<Is##CLASS##Op<L, R>::value>::type* = \
- nullptr> \
- constexpr CLASS##Numeric< \
- typename MathWrapper<CLASS##OP_NAME##Op, L, R>::type> \
- operator OP(const L lhs, const R rhs) { \
+ template <typename L, typename R> \
+ requires(Is##CLASS##Op<L, R>) \
+ constexpr CLASS##Numeric<typename MathWrapper<CLASS##OP_NAME##Op, L, \
+ R>::type> operator OP(L lhs, \
+ R rhs) { \
return decltype(lhs OP rhs)::template MathOp<CLASS##OP_NAME##Op>(lhs, \
rhs); \
} \
/* Assignment arithmetic operator implementation from CLASS##Numeric. */ \
template <typename L> \
+ requires std::is_arithmetic_v<L> \
template <typename R> \
- constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP( \
- const R rhs) { \
+ constexpr CLASS##Numeric<L>& CLASS##Numeric<L>::operator CMP_OP(R rhs) { \
return MathOp<CLASS##OP_NAME##Op>(rhs); \
} \
/* Variadic arithmetic functions that return CLASS##Numeric. */ \