Support promotion in comparison between Jet and scalars

Previously, Jet did not allow comparison between its value and a scalar
of another type. Specifically, the comparison was limited to scalars of
the same type effectively disabling standard promotion rules. Instead,
users would be required either to cast values to target Jet arithmetic
type or explicitly construct a Jet instance that can be eventually used
for comparison purposes.

However, such a behavior is not intuitive and causes problems with types
that rely on standard promotion rules (e.g., std::complex in some
implementations).

This changeset extends the comparison operators by enabling logical
comparisons between a Jet and values compatible to the underlying scalar
type (e.g., int). To be as generic as possible, the types allowed to be
passed to comparison operators are constrained using SFINAE. This in
turn allows a recursive expansion of jets and therefore the comparison
of a nested jets with a scalar.

The changes alleviate problems described in #414 and also allow the use
of special functions from Boost.Math, e.g., for computing the reciprocal
using boost::math::pow<-1>(...).

Change-Id: I3625791e6c8c2c0bfbffbbdb09e179a32e57f306
diff --git a/include/ceres/jet.h b/include/ceres/jet.h
index b3c65fa..9a66317 100644
--- a/include/ceres/jet.h
+++ b/include/ceres/jet.h
@@ -164,11 +164,53 @@
 #include <limits>
 #include <numeric>
 #include <string>
+#include <type_traits>
 
 #include "Eigen/Core"
 #include "ceres/internal/port.h"
 
 namespace ceres {
+// Jet foward declaration necessary for the following partial specialization of
+// std::common_type.
+template <typename T, int N>
+struct Jet;
+}  // namespace ceres
+
+// Here we provide partial specializations of std::common_type for the Jet class
+// to allow determining a Jet type with a common underlying arithmetic type.
+// Such an arithmetic type can be either a scalar or an another Jet. An example
+// for a common type, say, between a float and a Jet<double, N> is a Jet<double,
+// N> (i.e., std::common_type_t<float, ceres::Jet<double, N>> and
+// ceres::Jet<double, N> refer to the same type.)
+//
+// The partial specialization are also used for determining compatible types by
+// means of SFINAE and thus allow such types to be expressed as operands of
+// logical comparison operators. Missing (partial) specialization of
+// std::common_type for a particular (custom) type will therefore disable the
+// use of comparison operators defined by Ceres.
+//
+// Since these partial specializations are used as SFINAE constraints, they
+// enable standard promotion rules between various scalar types and consequently
+// their use in comparison against a Jet without providing implicit
+// conversions from a scalar, such as an int, to a Jet (see the implementation
+// of logical comparison operators below).
+
+template <typename T, int N, typename U>
+struct std::common_type<T, ceres::Jet<U, N>> {
+  using type = ceres::Jet<common_type_t<T, U>, N>;
+};
+
+template <typename T, int N, typename U>
+struct std::common_type<ceres::Jet<T, N>, U> {
+  using type = ceres::Jet<common_type_t<T, U>, N>;
+};
+
+template <typename T, int N, typename U>
+struct std::common_type<ceres::Jet<T, N>, ceres::Jet<U, N>> {
+  using type = ceres::Jet<common_type_t<T, U>, N>;
+};
+
+namespace ceres {
 
 template <typename T, int N>
 struct Jet {
@@ -354,19 +396,32 @@
   return Jet<T, N>(f.a * s_inverse, f.v * s_inverse);
 }
 
-// Binary comparison operators for both scalars and jets.
-#define CERES_DEFINE_JET_COMPARISON_OPERATOR(op)                    \
-  template <typename T, int N>                                      \
-  inline bool operator op(const Jet<T, N>& f, const Jet<T, N>& g) { \
-    return f.a op g.a;                                              \
-  }                                                                 \
-  template <typename T, int N>                                      \
-  inline bool operator op(const T& s, const Jet<T, N>& g) {         \
-    return s op g.a;                                                \
-  }                                                                 \
-  template <typename T, int N>                                      \
-  inline bool operator op(const Jet<T, N>& f, const T& s) {         \
-    return f.a op s;                                                \
+// Binary comparison operators for both scalars and jets. std::common_type_t is
+// used as an SFINAE constraint to selectively enable compatible operand types.
+// This allows comparison, for instance, against int literals without implicit
+// conversion. In case the Jet arithmetic type is a Jet itself, a recursive
+// expansion of Jet value is performed.
+#define CERES_DEFINE_JET_COMPARISON_OPERATOR(op)                               \
+  template <typename T, int N>                                                 \
+  constexpr bool operator op(                                                  \
+      const Jet<T, N>& f, const Jet<T, N>& g) noexcept(noexcept(f.a op g.a)) { \
+    return f.a op g.a;                                                         \
+  }                                                                            \
+  template <typename T,                                                        \
+            int N,                                                             \
+            typename U,                                                        \
+            std::common_type_t<T, U>* = nullptr>                               \
+  constexpr bool operator op(                                                  \
+      const U& s, const Jet<T, N>& g) noexcept(noexcept(s op g.a)) {           \
+    return s op g.a;                                                           \
+  }                                                                            \
+  template <typename T,                                                        \
+            int N,                                                             \
+            typename U,                                                        \
+            std::common_type_t<T, U>* = nullptr>                               \
+  constexpr bool operator op(const Jet<T, N>& f,                               \
+                             const U& s) noexcept(noexcept(f.a op s)) {        \
+    return f.a op s;                                                           \
   }
 CERES_DEFINE_JET_COMPARISON_OPERATOR(<)   // NOLINT
 CERES_DEFINE_JET_COMPARISON_OPERATOR(<=)  // NOLINT
diff --git a/internal/ceres/jet_test.cc b/internal/ceres/jet_test.cc
index 4680fd0..54834d1 100644
--- a/internal/ceres/jet_test.cc
+++ b/internal/ceres/jet_test.cc
@@ -1400,5 +1400,96 @@
   ExpectClose(e.v[0].v[0].v[0], kE, kTolerance);
 }
 
+#if GTEST_HAS_TYPED_TEST
+
+using Types = testing::Types<short,
+                             unsigned short,
+                             int,
+                             unsigned int,
+                             long,
+                             unsigned long,
+                             long long,
+                             unsigned long long,
+                             float,
+                             double,
+                             long double>;
+
+template <typename T>
+class JetTest : public testing::Test {};
+
+TYPED_TEST_SUITE(JetTest, Types);
+
+// Don't care about the dual part for comparison tests
+template <typename T>
+using J0 = Jet<T, 0>;
+using J0d = J0<double>;
+
+TYPED_TEST(JetTest, comparison_jet) {
+  using Scalar = TypeParam;
+
+  EXPECT_EQ(J0<Scalar>{0}, J0<Scalar>{0});
+  EXPECT_GE(J0<Scalar>{3}, J0<Scalar>{3});
+  EXPECT_GT(J0<Scalar>{3}, J0<Scalar>{2});
+  EXPECT_LE(J0<Scalar>{1}, J0<Scalar>{1});
+  EXPECT_LT(J0<Scalar>{1}, J0<Scalar>{2});
+  EXPECT_NE(J0<Scalar>{1}, J0<Scalar>{2});
+}
+
+TYPED_TEST(JetTest, comparison_scalar) {
+  using Scalar = TypeParam;
+
+  EXPECT_EQ(J0d{0.0}, Scalar{0});
+  EXPECT_GE(J0d{3.0}, Scalar{3});
+  EXPECT_GT(J0d{3.0}, Scalar{2});
+  EXPECT_LE(J0d{1.0}, Scalar{1});
+  EXPECT_LT(J0d{1.0}, Scalar{2});
+  EXPECT_NE(J0d{1.0}, Scalar{2});
+
+  EXPECT_EQ(Scalar{0}, J0d{0.0});
+  EXPECT_GE(Scalar{1}, J0d{1.0});
+  EXPECT_GT(Scalar{2}, J0d{1.0});
+  EXPECT_LE(Scalar{3}, J0d{3.0});
+  EXPECT_LT(Scalar{2}, J0d{3.0});
+  EXPECT_NE(Scalar{2}, J0d{1.0});
+}
+
+TYPED_TEST(JetTest, comparison_nested2x) {
+  using Scalar = TypeParam;
+
+  EXPECT_EQ(J0<J0d>{J0d{0.0}}, Scalar{0});
+  EXPECT_GE(J0<J0d>{J0d{3.0}}, Scalar{3});
+  EXPECT_GT(J0<J0d>{J0d{3.0}}, Scalar{2});
+  EXPECT_LE(J0<J0d>{J0d{1.0}}, Scalar{1});
+  EXPECT_LT(J0<J0d>{J0d{1.0}}, Scalar{2});
+  EXPECT_NE(J0<J0d>{J0d{1.0}}, Scalar{2});
+
+  EXPECT_EQ(Scalar{0}, J0<J0d>{J0d{0.0}});
+  EXPECT_GE(Scalar{1}, J0<J0d>{J0d{1.0}});
+  EXPECT_GT(Scalar{2}, J0<J0d>{J0d{1.0}});
+  EXPECT_LE(Scalar{3}, J0<J0d>{J0d{3.0}});
+  EXPECT_LT(Scalar{2}, J0<J0d>{J0d{3.0}});
+  EXPECT_NE(Scalar{2}, J0<J0d>{J0d{1.0}});
+}
+
+TYPED_TEST(JetTest, comparison_nested3x) {
+  using Scalar = TypeParam;
+
+  EXPECT_EQ(J0<J0<J0d>>{J0<J0d>{J0d{0.0}}}, Scalar{0});
+  EXPECT_GE(J0<J0<J0d>>{J0<J0d>{J0d{3.0}}}, Scalar{3});
+  EXPECT_GT(J0<J0<J0d>>{J0<J0d>{J0d{3.0}}}, Scalar{2});
+  EXPECT_LE(J0<J0<J0d>>{J0<J0d>{J0d{1.0}}}, Scalar{1});
+  EXPECT_LT(J0<J0<J0d>>{J0<J0d>{J0d{1.0}}}, Scalar{2});
+  EXPECT_NE(J0<J0<J0d>>{J0<J0d>{J0d{1.0}}}, Scalar{2});
+
+  EXPECT_EQ(Scalar{0}, J0<J0<J0d>>{J0<J0d>{J0d{0.0}}});
+  EXPECT_GE(Scalar{1}, J0<J0<J0d>>{J0<J0d>{J0d{1.0}}});
+  EXPECT_GT(Scalar{2}, J0<J0<J0d>>{J0<J0d>{J0d{1.0}}});
+  EXPECT_LE(Scalar{3}, J0<J0<J0d>>{J0<J0d>{J0d{3.0}}});
+  EXPECT_LT(Scalar{2}, J0<J0<J0d>>{J0<J0d>{J0d{3.0}}});
+  EXPECT_NE(Scalar{2}, J0<J0<J0d>>{J0<J0d>{J0d{1.0}}});
+}
+
+#endif  // GTEST_HAS_TYPED_TEST
+
 }  // namespace internal
 }  // namespace ceres