[go: nahoru, domu]

Add support for CheckedNumeric Max and Min constexpr functions

This also adds variadic helper functions CheckMax and CheckMin that
return the maximum or minimum (respectively) result of an arbitrary
number of arithmetic, CheckedNumeric, or StrictNumeric arguments.

Review-Url: https://codereview.chromium.org/2545253002
Cr-Commit-Position: refs/heads/master@{#436190}
diff --git a/base/numerics/safe_conversions.h b/base/numerics/safe_conversions.h
index 7140810..21fe3a80 100644
--- a/base/numerics/safe_conversions.h
+++ b/base/numerics/safe_conversions.h
@@ -42,6 +42,8 @@
 //      across any range of arithmetic types. StrictNumeric is the return type
 //      for values extracted from a CheckedNumeric class instance. The raw
 //      arithmetic value is extracted via static_cast to the underlying type.
+//  MakeStrictNum() - Creates a new StrictNumeric from the underlying type of
+//      the supplied arithmetic or StrictNumeric type.
 
 // Convenience function that returns true if the supplied value is in range
 // for the destination type.
@@ -229,6 +231,13 @@
   const T value_;
 };
 
+// Convience wrapper returns a StrictNumeric from the provided arithmetic type.
+template <typename T>
+constexpr StrictNumeric<typename UnderlyingType<T>::type> MakeStrictNum(
+    const T value) {
+  return value;
+}
+
 // Overload the ostream output operator to make logging work nicely.
 template <typename T>
 std::ostream& operator<<(std::ostream& os, const StrictNumeric<T>& value) {
@@ -258,6 +267,7 @@
 using internal::strict_cast;
 using internal::saturated_cast;
 using internal::StrictNumeric;
+using internal::MakeStrictNum;
 
 // Explicitly make a shorter size_t typedef for convenience.
 typedef StrictNumeric<size_t> SizeT;
diff --git a/base/numerics/safe_conversions_impl.h b/base/numerics/safe_conversions_impl.h
index c52b8f2..be9eaa1 100644
--- a/base/numerics/safe_conversions_impl.h
+++ b/base/numerics/safe_conversions_impl.h
@@ -335,11 +335,7 @@
   RIGHT_PROMOTION  // Use the type of the right-hand argument.
 };
 
-template <ArithmeticPromotionCategory Promotion,
-          typename Lhs,
-          typename Rhs = Lhs>
-struct ArithmeticPromotion;
-
+// Determines the type that can represent the largest positive value.
 template <typename Lhs,
           typename Rhs,
           ArithmeticPromotionCategory Promotion =
@@ -358,6 +354,34 @@
   using type = 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;
+};
+
+// Determines the type that is best able to represent an arithmetic result.
 template <typename Lhs,
           typename Rhs = Lhs,
           bool is_intmax_type =
diff --git a/base/numerics/safe_math.h b/base/numerics/safe_math.h
index 73bed4e..3d765c9 100644
--- a/base/numerics/safe_math.h
+++ b/base/numerics/safe_math.h
@@ -37,13 +37,21 @@
 //  CheckAnd() - Bitwise AND (integer only with unsigned result).
 //  CheckOr()  - Bitwise OR (integer only with unsigned result).
 //  CheckXor() - Bitwise XOR (integer only with unsigned result).
+//  CheckMax() - Maximum of supplied arguments.
+//  CheckMin() - Minimum of supplied arguments.
 //
 // The unary negation, increment, and decrement operators are supported, along
 // with the following unary arithmetic methods, which return a new
 // CheckedNumeric as a result of the operation:
 //  Abs() - Absolute value.
-//  UnsignedAbs() - Absolute value as an equival-width unsigned underlying type
+//  UnsignedAbs() - Absolute value as an equal-width unsigned underlying type
 //          (valid for only integral types).
+//  Max() - Returns whichever is greater of the current instance or argument.
+//          The underlying return type is whichever has the greatest magnitude.
+//  Min() - Returns whichever is lowest of the current instance or argument.
+//          The underlying return type is whichever has can represent the lowest
+//          number in the smallest width (e.g. int8_t over unsigned, int over
+//          int8_t, and float over int).
 //
 // The following methods convert from CheckedNumeric to standard numeric values:
 //  AssignIfValid() - Assigns the underlying value to the supplied destination
@@ -73,7 +81,7 @@
 //          derived from casting the current instance to a CheckedNumeric of
 //          the supplied destination type.
 //  CheckNum() - Creates a new CheckedNumeric from the underlying type of the
-//          supplied arithmetic or CheckedNumeric type.
+//          supplied arithmetic, CheckedNumeric, or StrictNumeric type.
 //
 // Comparison operations are explicitly not supported because they could result
 // in a crash on an unexpected CHECK condition. You should use patterns like the
@@ -220,6 +228,36 @@
     return CheckedNumeric<T>(value, is_valid);
   }
 
+  template <typename U>
+  constexpr CheckedNumeric<typename MathWrapper<CheckedMaxOp, T, U>::type> Max(
+      const U rhs) const {
+    using R = typename UnderlyingType<U>::type;
+    using result_type = typename MathWrapper<CheckedMaxOp, T, U>::type;
+    // TODO(jschuh): This can be converted to the MathOp version and remain
+    // constexpr once we have C++14 support.
+    return CheckedNumeric<result_type>(
+        static_cast<result_type>(
+            IsGreater<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
+                ? state_.value()
+                : Wrapper<U>::value(rhs)),
+        state_.is_valid() && Wrapper<U>::is_valid(rhs));
+  }
+
+  template <typename U>
+  constexpr CheckedNumeric<typename MathWrapper<CheckedMinOp, T, U>::type> Min(
+      const U rhs) const {
+    using R = typename UnderlyingType<U>::type;
+    using result_type = typename MathWrapper<CheckedMinOp, T, U>::type;
+    // TODO(jschuh): This can be converted to the MathOp version and remain
+    // constexpr once we have C++14 support.
+    return CheckedNumeric<result_type>(
+        static_cast<result_type>(
+            IsLess<T, R>::Test(state_.value(), Wrapper<U>::value(rhs))
+                ? state_.value()
+                : Wrapper<U>::value(rhs)),
+        state_.is_valid() && Wrapper<U>::is_valid(rhs));
+  }
+
   // This function is available only for integral types. It returns an unsigned
   // integer of the same width as the source type, containing the absolute value
   // of the source, and properly handling signed min.
@@ -300,6 +338,14 @@
       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) {
+      return static_cast<Src>(v);
+    }
+  };
 };
 
 // Convenience functions to avoid the ugly template disambiguator syntax.
@@ -350,7 +396,8 @@
 // Convience wrapper to return a new CheckedNumeric from the provided arithmetic
 // or CheckedNumericType.
 template <typename T>
-CheckedNumeric<typename UnderlyingType<T>::type> CheckNum(T value) {
+constexpr CheckedNumeric<typename UnderlyingType<T>::type> CheckNum(
+    const T value) {
   return value;
 }
 
@@ -363,8 +410,9 @@
   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,
           typename L,
           typename R,
@@ -376,8 +424,16 @@
                        : decltype(ChkMathOp<M>(tmp, args...))(tmp);
 };
 
-// This is just boilerplate for the standard arithmetic operator overloads.
-// A macro isn't the nicest solution, but it beats rewriting these repeatedly.
+// The following macros are just boilerplate for the standard arithmetic
+// operator overloads and variadic function templates. A macro isn't the nicest
+// solution, but it beats rewriting these over and over again.
+#define BASE_NUMERIC_ARITHMETIC_VARIADIC(NAME)                                \
+  template <typename L, typename R, typename... Args>                         \
+  CheckedNumeric<typename ResultType<Checked##NAME##Op, L, R, Args...>::type> \
+      Check##NAME(const L lhs, const R rhs, const Args... args) {             \
+    return ChkMathOp<Checked##NAME##Op, L, R, Args...>(lhs, rhs, args...);    \
+  }
+
 #define BASE_NUMERIC_ARITHMETIC_OPERATORS(NAME, OP, COMPOUND_OP)               \
   /* Binary arithmetic operator for all CheckedNumeric operations. */          \
   template <typename L, typename R,                                            \
@@ -394,11 +450,7 @@
     return MathOp<Checked##NAME##Op>(rhs);                                     \
   }                                                                            \
   /* Variadic arithmetic functions that return CheckedNumeric. */              \
-  template <typename L, typename R, typename... Args>                          \
-  CheckedNumeric<typename ResultType<Checked##NAME##Op, L, R, Args...>::type>  \
-      Check##NAME(const L lhs, const R rhs, const Args... args) {              \
-    return ChkMathOp<Checked##NAME##Op, L, R, Args...>(lhs, rhs, args...);     \
-  }
+  BASE_NUMERIC_ARITHMETIC_VARIADIC(NAME)
 
 BASE_NUMERIC_ARITHMETIC_OPERATORS(Add, +, +=)
 BASE_NUMERIC_ARITHMETIC_OPERATORS(Sub, -, -=)
@@ -410,7 +462,10 @@
 BASE_NUMERIC_ARITHMETIC_OPERATORS(And, &, &=)
 BASE_NUMERIC_ARITHMETIC_OPERATORS(Or, |, |=)
 BASE_NUMERIC_ARITHMETIC_OPERATORS(Xor, ^, ^=)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Max)
+BASE_NUMERIC_ARITHMETIC_VARIADIC(Min)
 
+#undef BASE_NUMERIC_ARITHMETIC_VARIADIC
 #undef BASE_NUMERIC_ARITHMETIC_OPERATORS
 
 // These are some extra StrictNumeric operators to support simple pointer
@@ -439,6 +494,8 @@
 using internal::ValueOrDieForType;
 using internal::ValueOrDefaultForType;
 using internal::CheckNum;
+using internal::CheckMax;
+using internal::CheckMin;
 using internal::CheckAdd;
 using internal::CheckSub;
 using internal::CheckMul;
diff --git a/base/numerics/safe_math_impl.h b/base/numerics/safe_math_impl.h
index 7aab62d..4b9877a 100644
--- a/base/numerics/safe_math_impl.h
+++ b/base/numerics/safe_math_impl.h
@@ -474,6 +474,46 @@
   }
 };
 
+// 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>
+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;
+  template <typename V = result_type>
+  static bool Do(T x, U y, V* result) {
+    *result = IsGreater<T, U>::Test(x, y) ? static_cast<result_type>(x)
+                                          : static_cast<result_type>(y);
+    return true;
+  }
+};
+
+// 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>
+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;
+  template <typename V = result_type>
+  static bool Do(T x, U y, V* result) {
+    *result = IsLess<T, U>::Test(x, y) ? static_cast<result_type>(x)
+                                       : static_cast<result_type>(y);
+    return true;
+  }
+};
+
 template <typename T>
 typename std::enable_if<std::numeric_limits<T>::is_integer &&
                             std::numeric_limits<T>::is_signed,
@@ -688,7 +728,12 @@
   constexpr CheckedNumericState(const CheckedNumericState<Src>& rhs)
       : value_(static_cast<T>(rhs.value())) {}
 
-  constexpr bool is_valid() const { return std::isfinite(value_); }
+  constexpr bool is_valid() const {
+    // Written this way because std::isfinite is not reliably constexpr.
+    // TODO(jschuh): Fix this if the libraries ever get fixed.
+    return value_ <= std::numeric_limits<T>::max() &&
+           value_ >= std::numeric_limits<T>::lowest();
+  }
   constexpr T value() const { return value_; }
 };
 
diff --git a/base/numerics/safe_numerics_unittest.cc b/base/numerics/safe_numerics_unittest.cc
index f1522ba..7d42a83 100644
--- a/base/numerics/safe_numerics_unittest.cc
+++ b/base/numerics/safe_numerics_unittest.cc
@@ -26,6 +26,8 @@
 using base::ValueOrDieForType;
 using base::ValueOrDefaultForType;
 using base::CheckNum;
+using base::CheckMax;
+using base::CheckMin;
 using base::CheckAdd;
 using base::CheckSub;
 using base::CheckMul;
@@ -38,9 +40,9 @@
 using base::IsValueNegative;
 using base::SizeT;
 using base::StrictNumeric;
+using base::MakeStrictNum;
 using base::saturated_cast;
 using base::strict_cast;
-using base::StrictNumeric;
 using base::internal::MaxExponent;
 using base::internal::RANGE_VALID;
 using base::internal::RANGE_INVALID;
@@ -438,37 +440,63 @@
 void TestStrictComparison() {
   typedef numeric_limits<Dst> DstLimits;
   typedef numeric_limits<Src> SrcLimits;
-  static_assert(StrictNumeric<Src>(SrcLimits::min()) < DstLimits::max(), "");
-  static_assert(StrictNumeric<Src>(SrcLimits::min()) < SrcLimits::max(), "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::min()) >= DstLimits::max()),
+  static_assert(StrictNumeric<Src>(SrcLimits::lowest()) < DstLimits::max(), "");
+  static_assert(StrictNumeric<Src>(SrcLimits::lowest()) < SrcLimits::max(), "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) >= DstLimits::max()),
                 "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::min()) >= SrcLimits::max()),
+  static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) >= SrcLimits::max()),
                 "");
-  static_assert(StrictNumeric<Src>(SrcLimits::min()) <= DstLimits::max(), "");
-  static_assert(StrictNumeric<Src>(SrcLimits::min()) <= SrcLimits::max(), "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::min()) > DstLimits::max()), "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::min()) > SrcLimits::max()), "");
-  static_assert(StrictNumeric<Src>(SrcLimits::max()) > DstLimits::min(), "");
-  static_assert(StrictNumeric<Src>(SrcLimits::max()) > SrcLimits::min(), "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= DstLimits::min()),
+  static_assert(StrictNumeric<Src>(SrcLimits::lowest()) <= DstLimits::max(),
                 "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= SrcLimits::min()),
+  static_assert(StrictNumeric<Src>(SrcLimits::lowest()) <= SrcLimits::max(),
                 "");
-  static_assert(StrictNumeric<Src>(SrcLimits::max()) >= DstLimits::min(), "");
-  static_assert(StrictNumeric<Src>(SrcLimits::max()) >= SrcLimits::min(), "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < DstLimits::min()), "");
-  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < SrcLimits::min()), "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) > DstLimits::max()),
+                "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::lowest()) > SrcLimits::max()),
+                "");
+  static_assert(StrictNumeric<Src>(SrcLimits::max()) > DstLimits::lowest(), "");
+  static_assert(StrictNumeric<Src>(SrcLimits::max()) > SrcLimits::lowest(), "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= DstLimits::lowest()),
+                "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) <= SrcLimits::lowest()),
+                "");
+  static_assert(StrictNumeric<Src>(SrcLimits::max()) >= DstLimits::lowest(),
+                "");
+  static_assert(StrictNumeric<Src>(SrcLimits::max()) >= SrcLimits::lowest(),
+                "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < DstLimits::lowest()),
+                "");
+  static_assert(!(StrictNumeric<Src>(SrcLimits::max()) < SrcLimits::lowest()),
+                "");
   static_assert(StrictNumeric<Src>(static_cast<Src>(1)) == static_cast<Dst>(1),
                 "");
   static_assert(StrictNumeric<Src>(static_cast<Src>(1)) != static_cast<Dst>(0),
                 "");
   static_assert(StrictNumeric<Src>(SrcLimits::max()) != static_cast<Dst>(0),
                 "");
-  static_assert(StrictNumeric<Src>(SrcLimits::max()) != DstLimits::min(), "");
+  static_assert(StrictNumeric<Src>(SrcLimits::max()) != DstLimits::lowest(),
+                "");
   static_assert(
       !(StrictNumeric<Src>(static_cast<Src>(1)) != static_cast<Dst>(1)), "");
   static_assert(
       !(StrictNumeric<Src>(static_cast<Src>(1)) == static_cast<Dst>(0)), "");
+
+  // Due to differences in float handling between compilers, these aren't
+  // compile-time constants everywhere. So, we use run-time tests.
+  EXPECT_EQ(SrcLimits::max(),
+            CheckNum(SrcLimits::max()).Max(DstLimits::lowest()).ValueOrDie());
+  EXPECT_EQ(DstLimits::max(),
+            CheckNum(SrcLimits::lowest()).Max(DstLimits::max()).ValueOrDie());
+  EXPECT_EQ(DstLimits::lowest(),
+            CheckNum(SrcLimits::max()).Min(DstLimits::lowest()).ValueOrDie());
+  EXPECT_EQ(SrcLimits::lowest(),
+            CheckNum(SrcLimits::lowest()).Min(DstLimits::max()).ValueOrDie());
+  EXPECT_EQ(SrcLimits::lowest(), CheckMin(MakeStrictNum(1), CheckNum(0),
+                                          DstLimits::max(), SrcLimits::lowest())
+                                     .ValueOrDie());
+  EXPECT_EQ(DstLimits::max(), CheckMax(MakeStrictNum(1), CheckNum(0),
+                                       DstLimits::max(), SrcLimits::lowest())
+                                  .ValueOrDie());
 }
 
 template <typename Dst, typename Src>