Add mixed dense/skew-symmetric arithmetic operators

libeigen/eigen!2308

Closes #2925
diff --git a/Eigen/src/Core/SkewSymmetricMatrix3.h b/Eigen/src/Core/SkewSymmetricMatrix3.h
index a6ad143..72b65e1 100644
--- a/Eigen/src/Core/SkewSymmetricMatrix3.h
+++ b/Eigen/src/Core/SkewSymmetricMatrix3.h
@@ -159,6 +159,61 @@
       const SkewSymmetricBase<OtherDerived>& other) const {
     return (vector() - other.vector()).asSkewSymmetric();
   }
+
+  // Return type of dense +/- skew. Scalar follows ScalarBinaryOpTraits, so
+  // mixed-scalar cases go through the same promotion machinery as the rest of
+  // Eigen (and are rejected at compile time unless a user-provided
+  // specialization makes them valid). Shape is always 3x3.
+  template <typename OtherDerived, typename BinaryOp>
+  using DenseSkewBinaryReturnType = Matrix<
+      typename ScalarBinaryOpTraits<typename internal::traits<OtherDerived>::Scalar, Scalar, BinaryOp>::ReturnType, 3,
+      3>;
+
+  template <typename OtherDerived>
+  using DenseSkewSumReturnType =
+      DenseSkewBinaryReturnType<OtherDerived,
+                                internal::scalar_sum_op<typename internal::traits<OtherDerived>::Scalar, Scalar>>;
+
+  template <typename OtherDerived>
+  using DenseSkewDifferenceReturnType = DenseSkewBinaryReturnType<
+      OtherDerived, internal::scalar_difference_op<typename internal::traits<OtherDerived>::Scalar, Scalar>>;
+
+  /** \returns the sum of a dense matrix \a lhs and the skew symmetric matrix \a rhs as a dense matrix.
+   *
+   * The LHS must be 3x3 at compile time (or Dynamic, which is checked at runtime by the conversion).
+   * Only the skew side is materialized via \c toDenseMatrix(); the LHS remains a lazy expression
+   * until the enclosing assignment. */
+  template <typename OtherDerived>
+  EIGEN_DEVICE_FUNC friend EIGEN_STRONG_INLINE DenseSkewSumReturnType<OtherDerived> operator+(
+      const MatrixBase<OtherDerived>& lhs, const SkewSymmetricBase& rhs) {
+    EIGEN_STATIC_ASSERT_SAME_MATRIX_SIZE(OtherDerived, DenseMatrixType);
+    return lhs.derived() + rhs.toDenseMatrix();
+  }
+
+  /** \returns the sum of the skew symmetric matrix \a lhs and a dense matrix \a rhs as a dense matrix.
+   *
+   * Sum is commutative, so this forwards to the \c dense+skew overload. */
+  template <typename OtherDerived>
+  EIGEN_DEVICE_FUNC friend EIGEN_STRONG_INLINE DenseSkewSumReturnType<OtherDerived> operator+(
+      const SkewSymmetricBase& lhs, const MatrixBase<OtherDerived>& rhs) {
+    return rhs + lhs;
+  }
+
+  /** \returns the difference of a dense matrix \a lhs and the skew symmetric matrix \a rhs as a dense matrix. */
+  template <typename OtherDerived>
+  EIGEN_DEVICE_FUNC friend EIGEN_STRONG_INLINE DenseSkewDifferenceReturnType<OtherDerived> operator-(
+      const MatrixBase<OtherDerived>& lhs, const SkewSymmetricBase& rhs) {
+    EIGEN_STATIC_ASSERT_SAME_MATRIX_SIZE(OtherDerived, DenseMatrixType);
+    return lhs.derived() - rhs.toDenseMatrix();
+  }
+
+  /** \returns the difference of the skew symmetric matrix \a lhs and a dense matrix \a rhs as a dense matrix. */
+  template <typename OtherDerived>
+  EIGEN_DEVICE_FUNC friend EIGEN_STRONG_INLINE DenseSkewDifferenceReturnType<OtherDerived> operator-(
+      const SkewSymmetricBase& lhs, const MatrixBase<OtherDerived>& rhs) {
+    EIGEN_STATIC_ASSERT_SAME_MATRIX_SIZE(OtherDerived, DenseMatrixType);
+    return lhs.toDenseMatrix() - rhs.derived();
+  }
 };
 
 /** \class SkewSymmetricMatrix3
diff --git a/test/skew_symmetric_matrix3.cpp b/test/skew_symmetric_matrix3.cpp
index 48d490d..a7fcb47 100644
--- a/test/skew_symmetric_matrix3.cpp
+++ b/test/skew_symmetric_matrix3.cpp
@@ -81,6 +81,44 @@
   VERIFY_IS_APPROX((sk1 + sk1).vector(), 2 * v1);
   VERIFY((sk1 - sk1).vector().isZero());
   VERIFY((sk1 - sk1).toDenseMatrix().isZero());
+
+  // Mixed dense / skew-symmetric +/- operators (see issue #2925, MR !2308).
+  // Cases c1/c2/c3 below are exactly the three expressions from the issue.
+  {
+    const SquareMatrix A = SquareMatrix::Random();
+    const Vector b = Vector::Random();
+
+    const SquareMatrix skA = (A * b).asSkewSymmetric().toDenseMatrix();
+    const SquareMatrix skB = v1.asSkewSymmetric().toDenseMatrix();
+    const SquareMatrix skB_times_A = v1.asSkewSymmetric().toDenseMatrix() * A;
+    const SquareMatrix A_times_skB = A * v1.asSkewSymmetric().toDenseMatrix();
+
+    // c1: dense - skew
+    const SquareMatrix c1 = A - SkewSymmetricMatrix3<Scalar>(A * b);
+    VERIFY_IS_APPROX(c1, A - skA);
+
+    // c2: Product<dense,skew> - skew
+    const SquareMatrix c2 = A * SkewSymmetricMatrix3<Scalar>(v1) - SkewSymmetricMatrix3<Scalar>(A * b);
+    VERIFY_IS_APPROX(c2, A_times_skB - skA);
+
+    // c3: Product<skew,dense> - skew
+    const SquareMatrix c3 = SkewSymmetricMatrix3<Scalar>(v1) * A - SkewSymmetricMatrix3<Scalar>(A * b);
+    VERIFY_IS_APPROX(c3, skB_times_A - skA);
+
+    // M + S and S + M (commutativity check).
+    VERIFY_IS_APPROX(SquareMatrix(A + sk1), A + skB);
+    VERIFY_IS_APPROX(SquareMatrix(sk1 + A), skB + A);
+    VERIFY_IS_APPROX(SquareMatrix(A + sk1), SquareMatrix(sk1 + A));
+
+    // M - S and S - M (anti-commutativity sanity).
+    VERIFY_IS_APPROX(SquareMatrix(A - sk1), A - skB);
+    VERIFY_IS_APPROX(SquareMatrix(sk1 - A), skB - A);
+    VERIFY_IS_APPROX(SquareMatrix(A - sk1), -SquareMatrix(sk1 - A));
+
+    // Product expression on the LHS should still fuse (no explicit
+    // .toDenseMatrix() needed on the dense side).
+    VERIFY_IS_APPROX(SquareMatrix(A * A - sk1), (A * A).eval() - skB);
+  }
 }
 
 template <typename Scalar>