]> git-server-git.apps.pok.os.sepia.ceph.com Git - googletest.git/commitdiff
Enable safe matcher casts from `Matcher<const T&>` to `Matcher<T>`.
authorAbseil Team <absl-team@google.com>
Wed, 15 Jan 2025 17:16:21 +0000 (09:16 -0800)
committerCopybara-Service <copybara-worker@google.com>
Wed, 15 Jan 2025 17:16:57 +0000 (09:16 -0800)
PiperOrigin-RevId: 715826130
Change-Id: Id962fd456f6da21ea2a909f331f92d814f1dad46

docs/gmock_cook_book.md
googlemock/include/gmock/gmock-matchers.h
googlemock/test/gmock-matchers-comparisons_test.cc
googlemock/test/gmock-matchers-misc_test.cc

index 948abe9beb1e783a864e167b88247e84ec79c7e0..633dbc37f544aa98e675aef967b90761feef1b22 100644 (file)
@@ -936,8 +936,8 @@ casts a matcher `m` to type `Matcher<T>`. To ensure safety, gMock checks that
     floating-point numbers), the conversion from `T` to `U` is not lossy (in
     other words, any value representable by `T` can also be represented by `U`);
     and
-3.  When `U` is a reference, `T` must also be a reference (as the underlying
-    matcher may be interested in the address of the `U` value).
+3.  When `U` is a non-const reference, `T` must also be a reference (as the
+    underlying matcher may be interested in the address of the `U` value).
 
 The code won't compile if any of these conditions isn't met.
 
index 8f82b273be25ced477dd026d2a8c3b1781a1ccbf..18cbe1601a717aaf14b4ef41b9616a24042094e8 100644 (file)
@@ -408,13 +408,22 @@ class MatcherCastImpl<T, Matcher<U>> {
   }
 
  private:
-  class Impl : public MatcherInterface<T> {
+  // If it's possible to implicitly convert a `const T&` to U, then `Impl` can
+  // take that as input to avoid a copy. Otherwise, such as when `T` is a
+  // non-const reference type or a type explicitly constructible only from a
+  // non-const reference, then `Impl` must use `T` as-is (potentially copying).
+  using ImplArgT =
+      typename std::conditional<std::is_convertible<const T&, const U&>::value,
+                                const T&, T>::type;
+
+  class Impl : public MatcherInterface<ImplArgT> {
    public:
     explicit Impl(const Matcher<U>& source_matcher)
         : source_matcher_(source_matcher) {}
 
     // We delegate the matching logic to the source matcher.
-    bool MatchAndExplain(T x, MatchResultListener* listener) const override {
+    bool MatchAndExplain(ImplArgT x,
+                         MatchResultListener* listener) const override {
       using FromType = typename std::remove_cv<typename std::remove_pointer<
           typename std::remove_reference<T>::type>::type>::type;
       using ToType = typename std::remove_cv<typename std::remove_pointer<
@@ -431,9 +440,8 @@ class MatcherCastImpl<T, Matcher<U>> {
 
       // Do the cast to `U` explicitly if necessary.
       // Otherwise, let implicit conversions do the trick.
-      using CastType =
-          typename std::conditional<std::is_convertible<T&, const U&>::value,
-                                    T&, U>::type;
+      using CastType = typename std::conditional<
+          std::is_convertible<ImplArgT&, const U&>::value, ImplArgT&, U>::type;
 
       return source_matcher_.MatchAndExplain(static_cast<CastType>(x),
                                              listener);
@@ -528,18 +536,16 @@ inline Matcher<T> SafeMatcherCast(const M& polymorphic_matcher_or_value) {
 // safely convert a Matcher<U> to a Matcher<T> (i.e. Matcher is
 // contravariant): just keep a copy of the original Matcher<U>, convert the
 // argument from type T to U, and then pass it to the underlying Matcher<U>.
-// The only exception is when U is a reference and T is not, as the
+// The only exception is when U is a non-const reference and T is not, as the
 // underlying Matcher<U> may be interested in the argument's address, which
-// is not preserved in the conversion from T to U.
+// cannot be preserved in the conversion from T to U (since a copy of the input
+// T argument would be required to provide a non-const reference U).
 template <typename T, typename U>
 inline Matcher<T> SafeMatcherCast(const Matcher<U>& matcher) {
   // Enforce that T can be implicitly converted to U.
   static_assert(std::is_convertible<const T&, const U&>::value,
-                "T must be implicitly convertible to U");
-  // Enforce that we are not converting a non-reference type T to a reference
-  // type U.
-  static_assert(std::is_reference<T>::value || !std::is_reference<U>::value,
-                "cannot convert non reference arg to reference");
+                "T must be implicitly convertible to U (and T must be a "
+                "non-const reference if U is a non-const reference)");
   // In case both T and U are arithmetic types, enforce that the
   // conversion is not lossy.
   typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT;
index a324c4c73c06ed149d9da8ff7633c4be11787d6d..a331aeca96600f73a4aec086e3e4b54ac932c074 100644 (file)
@@ -411,9 +411,27 @@ class IntValue {
   int value_;
 };
 
+// For testing casting matchers between compatible types. This is similar to
+// IntValue, but takes a non-const reference to the value, showing MatcherCast
+// works with such types (and doesn't, for example, use a const ref internally).
+class MutableIntView {
+ public:
+  // An int& can be statically (although not implicitly) cast to a
+  // MutableIntView.
+  explicit MutableIntView(int& a_value) : value_(a_value) {}
+
+  int& value() const { return value_; }
+
+ private:
+  int& value_;
+};
+
 // For testing casting matchers between compatible types.
 bool IsPositiveIntValue(const IntValue& foo) { return foo.value() > 0; }
 
+// For testing casting matchers between compatible types.
+bool IsPositiveMutableIntView(MutableIntView foo) { return foo.value() > 0; }
+
 // Tests that MatcherCast<T>(m) works when m is a Matcher<U> where T
 // can be statically converted to U.
 TEST(MatcherCastTest, FromCompatibleType) {
@@ -429,14 +447,34 @@ TEST(MatcherCastTest, FromCompatibleType) {
   // predicate.
   EXPECT_TRUE(m4.Matches(1));
   EXPECT_FALSE(m4.Matches(0));
+
+  Matcher<MutableIntView> m5 = Truly(IsPositiveMutableIntView);
+  Matcher<int> m6 = MatcherCast<int>(m5);
+  // In the following, the arguments 1 and 0 are statically converted to
+  // MutableIntView objects, and then tested by the IsPositiveMutableIntView()
+  // predicate.
+  EXPECT_TRUE(m6.Matches(1));
+  EXPECT_FALSE(m6.Matches(0));
 }
 
 // Tests that MatcherCast<T>(m) works when m is a Matcher<const T&>.
 TEST(MatcherCastTest, FromConstReferenceToNonReference) {
-  Matcher<const int&> m1 = Eq(0);
+  int n = 0;
+  Matcher<const int&> m1 = Ref(n);
   Matcher<int> m2 = MatcherCast<int>(m1);
-  EXPECT_TRUE(m2.Matches(0));
-  EXPECT_FALSE(m2.Matches(1));
+  int n1 = 0;
+  EXPECT_TRUE(m2.Matches(n));
+  EXPECT_FALSE(m2.Matches(n1));
+}
+
+// Tests that MatcherCast<T&>(m) works when m is a Matcher<const T&>.
+TEST(MatcherCastTest, FromConstReferenceToReference) {
+  int n = 0;
+  Matcher<const int&> m1 = Ref(n);
+  Matcher<int&> m2 = MatcherCast<int&>(m1);
+  int n1 = 0;
+  EXPECT_TRUE(m2.Matches(n));
+  EXPECT_FALSE(m2.Matches(n1));
 }
 
 // Tests that MatcherCast<T>(m) works when m is a Matcher<T&>.
@@ -445,6 +483,12 @@ TEST(MatcherCastTest, FromReferenceToNonReference) {
   Matcher<int> m2 = MatcherCast<int>(m1);
   EXPECT_TRUE(m2.Matches(0));
   EXPECT_FALSE(m2.Matches(1));
+
+  // Of course, reference identity isn't preserved since a copy is required.
+  int n = 0;
+  Matcher<int&> m3 = Ref(n);
+  Matcher<int> m4 = MatcherCast<int>(m3);
+  EXPECT_FALSE(m4.Matches(n));
 }
 
 // Tests that MatcherCast<const T&>(m) works when m is a Matcher<T>.
@@ -649,6 +693,16 @@ TEST(SafeMatcherCastTest, FromBaseClass) {
   EXPECT_FALSE(m4.Matches(d2));
 }
 
+// Tests that SafeMatcherCast<T>(m) works when m is a Matcher<const T&>.
+TEST(SafeMatcherCastTest, FromConstReferenceToNonReference) {
+  int n = 0;
+  Matcher<const int&> m1 = Ref(n);
+  Matcher<int> m2 = SafeMatcherCast<int>(m1);
+  int n1 = 0;
+  EXPECT_TRUE(m2.Matches(n));
+  EXPECT_FALSE(m2.Matches(n1));
+}
+
 // Tests that SafeMatcherCast<T&>(m) works when m is a Matcher<const T&>.
 TEST(SafeMatcherCastTest, FromConstReferenceToReference) {
   int n = 0;
index ac976dd3e693820a1a866598b80df55f17ebabb8..de8b76c69af47b291446b399b9a4a72706dd8fbf 100644 (file)
@@ -32,6 +32,7 @@
 // This file tests some commonly used argument matchers.
 
 #include <array>
+#include <cstdint>
 #include <memory>
 #include <ostream>
 #include <string>
@@ -747,6 +748,24 @@ TYPED_TEST(OptionalTest, DoesNotMatchNullopt) {
   EXPECT_FALSE(m.Matches(empty));
 }
 
+TYPED_TEST(OptionalTest, ComposesWithMonomorphicMatchersTakingReferences) {
+  const Matcher<const int&> eq1 = Eq(1);
+  const Matcher<const int&> eq2 = Eq(2);
+  TypeParam opt(1);
+  EXPECT_THAT(opt, Optional(eq1));
+  EXPECT_THAT(opt, Optional(Not(eq2)));
+  EXPECT_THAT(opt, Optional(AllOf(eq1, Not(eq2))));
+}
+
+TYPED_TEST(OptionalTest, ComposesWithMonomorphicMatchersRequiringConversion) {
+  const Matcher<int64_t> eq1 = Eq(1);
+  const Matcher<int64_t> eq2 = Eq(2);
+  TypeParam opt(1);
+  EXPECT_THAT(opt, Optional(eq1));
+  EXPECT_THAT(opt, Optional(Not(eq2)));
+  EXPECT_THAT(opt, Optional(AllOf(eq1, Not(eq2))));
+}
+
 template <typename T>
 class MoveOnlyOptionalTest : public testing::Test {};