Enable safe matcher casts from `Matcher<const T&>` to `Matcher<T>`.

PiperOrigin-RevId: 715826130
Change-Id: Id962fd456f6da21ea2a909f331f92d814f1dad46
pull/4702/head
Abseil Team 4 months ago committed by Copybara-Service
parent 504ea69cf7
commit e4ece4881d
  1. 4
      docs/gmock_cook_book.md
  2. 30
      googlemock/include/gmock/gmock-matchers.h
  3. 60
      googlemock/test/gmock-matchers-comparisons_test.cc
  4. 19
      googlemock/test/gmock-matchers-misc_test.cc

@ -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 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`); other words, any value representable by `T` can also be represented by `U`);
and and
3. When `U` is a reference, `T` must also be a reference (as the underlying 3. When `U` is a non-const reference, `T` must also be a reference (as the
matcher may be interested in the address of the `U` value). 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. The code won't compile if any of these conditions isn't met.

@ -408,13 +408,22 @@ class MatcherCastImpl<T, Matcher<U>> {
} }
private: 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: public:
explicit Impl(const Matcher<U>& source_matcher) explicit Impl(const Matcher<U>& source_matcher)
: source_matcher_(source_matcher) {} : source_matcher_(source_matcher) {}
// We delegate the matching logic to the 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< using FromType = typename std::remove_cv<typename std::remove_pointer<
typename std::remove_reference<T>::type>::type>::type; typename std::remove_reference<T>::type>::type>::type;
using ToType = typename std::remove_cv<typename std::remove_pointer< 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. // Do the cast to `U` explicitly if necessary.
// Otherwise, let implicit conversions do the trick. // Otherwise, let implicit conversions do the trick.
using CastType = using CastType = typename std::conditional<
typename std::conditional<std::is_convertible<T&, const U&>::value, std::is_convertible<ImplArgT&, const U&>::value, ImplArgT&, U>::type;
T&, U>::type;
return source_matcher_.MatchAndExplain(static_cast<CastType>(x), return source_matcher_.MatchAndExplain(static_cast<CastType>(x),
listener); 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 // 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 // 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>. // 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 // 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> template <typename T, typename U>
inline Matcher<T> SafeMatcherCast(const Matcher<U>& matcher) { inline Matcher<T> SafeMatcherCast(const Matcher<U>& matcher) {
// Enforce that T can be implicitly converted to U. // Enforce that T can be implicitly converted to U.
static_assert(std::is_convertible<const T&, const U&>::value, static_assert(std::is_convertible<const T&, const U&>::value,
"T must be implicitly convertible to U"); "T must be implicitly convertible to U (and T must be a "
// Enforce that we are not converting a non-reference type T to a reference "non-const reference if U is a non-const reference)");
// type U.
static_assert(std::is_reference<T>::value || !std::is_reference<U>::value,
"cannot convert non reference arg to reference");
// In case both T and U are arithmetic types, enforce that the // In case both T and U are arithmetic types, enforce that the
// conversion is not lossy. // conversion is not lossy.
typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT; typedef GTEST_REMOVE_REFERENCE_AND_CONST_(T) RawT;

@ -411,9 +411,27 @@ class IntValue {
int value_; 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. // For testing casting matchers between compatible types.
bool IsPositiveIntValue(const IntValue& foo) { return foo.value() > 0; } 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 // Tests that MatcherCast<T>(m) works when m is a Matcher<U> where T
// can be statically converted to U. // can be statically converted to U.
TEST(MatcherCastTest, FromCompatibleType) { TEST(MatcherCastTest, FromCompatibleType) {
@ -429,14 +447,34 @@ TEST(MatcherCastTest, FromCompatibleType) {
// predicate. // predicate.
EXPECT_TRUE(m4.Matches(1)); EXPECT_TRUE(m4.Matches(1));
EXPECT_FALSE(m4.Matches(0)); 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&>. // Tests that MatcherCast<T>(m) works when m is a Matcher<const T&>.
TEST(MatcherCastTest, FromConstReferenceToNonReference) { TEST(MatcherCastTest, FromConstReferenceToNonReference) {
Matcher<const int&> m1 = Eq(0); int n = 0;
Matcher<const int&> m1 = Ref(n);
Matcher<int> m2 = MatcherCast<int>(m1); Matcher<int> m2 = MatcherCast<int>(m1);
EXPECT_TRUE(m2.Matches(0)); int n1 = 0;
EXPECT_FALSE(m2.Matches(1)); 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&>. // 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); Matcher<int> m2 = MatcherCast<int>(m1);
EXPECT_TRUE(m2.Matches(0)); EXPECT_TRUE(m2.Matches(0));
EXPECT_FALSE(m2.Matches(1)); 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>. // 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)); 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&>. // Tests that SafeMatcherCast<T&>(m) works when m is a Matcher<const T&>.
TEST(SafeMatcherCastTest, FromConstReferenceToReference) { TEST(SafeMatcherCastTest, FromConstReferenceToReference) {
int n = 0; int n = 0;

@ -32,6 +32,7 @@
// This file tests some commonly used argument matchers. // This file tests some commonly used argument matchers.
#include <array> #include <array>
#include <cstdint>
#include <memory> #include <memory>
#include <ostream> #include <ostream>
#include <string> #include <string>
@ -747,6 +748,24 @@ TYPED_TEST(OptionalTest, DoesNotMatchNullopt) {
EXPECT_FALSE(m.Matches(empty)); 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> template <typename T>
class MoveOnlyOptionalTest : public testing::Test {}; class MoveOnlyOptionalTest : public testing::Test {};

Loading…
Cancel
Save