diff --git a/docs/reference/matchers.md b/docs/reference/matchers.md index 243e3f9516..48978627b3 100644 --- a/docs/reference/matchers.md +++ b/docs/reference/matchers.md @@ -208,6 +208,15 @@ messages, you can use: | `Pointee(m)` | `argument` (either a smart pointer or a raw pointer) points to a value that matches matcher `m`. | | `Pointer(m)` | `argument` (either a smart pointer or a raw pointer) contains a pointer that matches `m`. `m` will match against the raw pointer regardless of the type of `argument`. | | `WhenDynamicCastTo<T>(m)` | when `argument` is passed through `dynamic_cast<T>()`, it matches matcher `m`. | +| `WhenStaticCastTo<T>(m)` | when `argument` is passed through `static_cast<T>()`, it matches matcher `m`. | + +`WhenDynamicCast` can be used for safely checking the dynamic type of an object +and navigating the inheritance tree of an object. + +`WhenStaticCast` is primarily used to check and argument which was type-erased +as `void*`. It can also be used as an unsafe replacement of `WhenDynamicCast` +in environemnts without RTTI, or for pointer-based type punning by chaining +a cast to `void*` and then another pointer (equivalent to `reinterpret_cast`). ## Multi-argument Matchers {#MultiArgMatchers} diff --git a/googlemock/include/gmock/gmock-matchers.h b/googlemock/include/gmock/gmock-matchers.h index e979544c72..d9c6ef3da0 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -2022,17 +2022,10 @@ class PointerMatcher { const InnerMatcher matcher_; }; -#if GTEST_HAS_RTTI -// Implements the WhenDynamicCastTo<T>(m) matcher that matches a pointer or -// reference that matches inner_matcher when dynamic_cast<T> is applied. -// The result of dynamic_cast<To> is forwarded to the inner matcher. -// If To is a pointer and the cast fails, the inner matcher will receive NULL. -// If To is a reference and the cast fails, this matcher returns false -// immediately. -template <typename To> -class WhenDynamicCastToMatcherBase { +template <typename To, typename Caster> +class WhenCastToMatcherBase { public: - explicit WhenDynamicCastToMatcherBase(const Matcher<To>& matcher) + explicit WhenCastToMatcherBase(const Matcher<To>& matcher) : matcher_(matcher) {} void DescribeTo(::std::ostream* os) const { @@ -2045,6 +2038,12 @@ class WhenDynamicCastToMatcherBase { matcher_.DescribeNegationTo(os); } + template <typename From> + bool MatchAndExplain(From&& from, MatchResultListener* listener) const { + decltype(auto) to = Caster::template Cast<To>(from); + return MatchPrintAndExplain(to, this->matcher_, listener); + } + protected: const Matcher<To> matcher_; @@ -2052,32 +2051,40 @@ class WhenDynamicCastToMatcherBase { private: static void GetCastTypeDescription(::std::ostream* os) { - *os << "when dynamic_cast to " << GetToName() << ", "; + *os << "when " << Caster::Name << " to " << GetToName() << ", "; } }; -// Primary template. -// To is a pointer. Cast and forward the result. -template <typename To> -class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase<To> { +#if GTEST_HAS_RTTI +class DynamicCaster { public: - explicit WhenDynamicCastToMatcher(const Matcher<To>& matcher) - : WhenDynamicCastToMatcherBase<To>(matcher) {} + static constexpr const char* Name = "dynamic_cast"; - template <typename From> - bool MatchAndExplain(From from, MatchResultListener* listener) const { - To to = dynamic_cast<To>(from); - return MatchPrintAndExplain(to, this->matcher_, listener); + template <typename To, typename From> + static To Cast(From&& from) { + return dynamic_cast<To>(from); } }; +// Implements the WhenDynamicCastTo<T>(m) matcher that matches a pointer or +// reference that matches inner_matcher when dynamic_cast<T> is applied. +// The result of dynamic_cast<To> is forwarded to the inner matcher. + +// To is a pointer. Cast and forward the result, which might be nullptr. +template <typename To> +class WhenDynamicCastToMatcher + : public WhenCastToMatcherBase<To, DynamicCaster> { + public: + using WhenCastToMatcherBase<To, DynamicCaster>::WhenCastToMatcherBase; +}; + // Specialize for references. // In this case we return false if the dynamic_cast fails. template <typename To> -class WhenDynamicCastToMatcher<To&> : public WhenDynamicCastToMatcherBase<To&> { +class WhenDynamicCastToMatcher<To&> + : public WhenCastToMatcherBase<To&, DynamicCaster> { public: - explicit WhenDynamicCastToMatcher(const Matcher<To&>& matcher) - : WhenDynamicCastToMatcherBase<To&>(matcher) {} + using WhenCastToMatcherBase<To&, DynamicCaster>::WhenCastToMatcherBase; template <typename From> bool MatchAndExplain(From& from, MatchResultListener* listener) const { @@ -2092,6 +2099,25 @@ class WhenDynamicCastToMatcher<To&> : public WhenDynamicCastToMatcherBase<To&> { }; #endif // GTEST_HAS_RTTI +// Implements the WhenStaticCastTo<T>(m) matcher that matches a pointer or +// reference that matches inner_matcher when static_cast<T> is applied. +// The result of static_cast<To> is forwarded to the inner matcher. +class StaticCaster { + public: + static constexpr const char* Name = "static_cast"; + + template <typename To, typename From> + static To Cast(From&& from) { + return static_cast<To>(from); + } +}; + +template <typename To> +class WhenStaticCastToMatcher : public WhenCastToMatcherBase<To, StaticCaster> { + public: + using WhenCastToMatcherBase<To, StaticCaster>::WhenCastToMatcherBase; +}; + // Implements the Field() matcher for matching a field (i.e. member // variable) of an object. template <typename Class, typename FieldType> @@ -4424,6 +4450,16 @@ WhenDynamicCastTo(const Matcher<To>& inner_matcher) { } #endif // GTEST_HAS_RTTI +// Creates a matcher that matches a pointer or reference that matches +// inner_matcher when static_cast<To> is applied. +// The result of static_cast<To> is forwarded to the inner matcher. +template <typename To> +inline PolymorphicMatcher<internal::WhenStaticCastToMatcher<To>> +WhenStaticCastTo(const Matcher<To>& inner_matcher) { + return MakePolymorphicMatcher( + internal::WhenStaticCastToMatcher<To>(inner_matcher)); +} + // Creates a matcher that matches an object whose given field matches // 'matcher'. For example, // Field(&Foo::number, Ge(5)) diff --git a/googlemock/test/gmock-matchers-comparisons_test.cc b/googlemock/test/gmock-matchers-comparisons_test.cc index a331aeca96..73307a2101 100644 --- a/googlemock/test/gmock-matchers-comparisons_test.cc +++ b/googlemock/test/gmock-matchers-comparisons_test.cc @@ -2333,6 +2333,41 @@ TEST(WhenDynamicCastToTest, BadReference) { } #endif // GTEST_HAS_RTTI +TEST(WhenStaticCastToTest, VoidPointer) { + Derived derived; + derived.i = 4; + void* as_void_ptr = &derived; + EXPECT_THAT(as_void_ptr, WhenStaticCastTo<Derived*>(Pointee(FieldIIs(4)))); + EXPECT_THAT(as_void_ptr, + WhenStaticCastTo<Derived*>(Pointee(Not(FieldIIs(5))))); +} + +TEST(WhenStaticCastToTest, Inheritance) { + Derived derived; + EXPECT_THAT(derived, WhenStaticCastTo<const Base&>(_)); + EXPECT_THAT(&derived, WhenStaticCastTo<Base*>(_)); + EXPECT_THAT(derived, WhenStaticCastTo<const Derived&>(_)); + EXPECT_THAT(&derived, WhenStaticCastTo<Derived*>(_)); + // These will not compile because direct sidecasts are invalid + // EXPECT_THAT(derived, WhenStaticCastTo<const OtherDerived&>(_)); + // EXPECT_THAT(&derived, WhenStaticCastTo<OtherDerived*>(_)); + + Base& as_base_ref = derived; + EXPECT_THAT(as_base_ref, WhenStaticCastTo<const Base&>(_)); + EXPECT_THAT(&as_base_ref, WhenStaticCastTo<Base*>(_)); + EXPECT_THAT(as_base_ref, WhenStaticCastTo<const Derived&>(_)); + EXPECT_THAT(&as_base_ref, WhenStaticCastTo<Derived*>(_)); + // These will compile and match, but are not safe + EXPECT_THAT(as_base_ref, WhenStaticCastTo<const OtherDerived&>(_)); + EXPECT_THAT(&as_base_ref, WhenStaticCastTo<OtherDerived*>(_)); +} + +TEST(WhenStaticCastToTest, Punning) { + std::uint32_t u32 = 0xCCCCCCCC; + EXPECT_THAT(&u32, WhenStaticCastTo<void*>( + WhenStaticCastTo<std::uint8_t*>(Pointee(Eq(0xCC))))); +} + class DivisibleByImpl { public: explicit DivisibleByImpl(int a_divider) : divider_(a_divider) {}