diff --git a/docs/reference/matchers.md b/docs/reference/matchers.md index 8ff9e0bc..cc5dda4a 100644 --- a/docs/reference/matchers.md +++ b/docs/reference/matchers.md @@ -211,6 +211,15 @@ contract of the function. | `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(m)` | when `argument` is passed through `dynamic_cast()`, it matches matcher `m`. | +| `WhenStaticCastTo(m)` | when `argument` is passed through `static_cast()`, 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 402dd699..983f221b 100644 --- a/googlemock/include/gmock/gmock-matchers.h +++ b/googlemock/include/gmock/gmock-matchers.h @@ -2023,17 +2023,10 @@ class PointerMatcher { const InnerMatcher matcher_; }; -#if GTEST_HAS_RTTI -// Implements the WhenDynamicCastTo(m) matcher that matches a pointer or -// reference that matches inner_matcher when dynamic_cast is applied. -// The result of dynamic_cast 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 -class WhenDynamicCastToMatcherBase { +template +class WhenCastToMatcherBase { public: - explicit WhenDynamicCastToMatcherBase(const Matcher& matcher) + explicit WhenCastToMatcherBase(const Matcher& matcher) : matcher_(matcher) {} void DescribeTo(::std::ostream* os) const { @@ -2046,6 +2039,12 @@ class WhenDynamicCastToMatcherBase { matcher_.DescribeNegationTo(os); } + template + bool MatchAndExplain(From&& from, MatchResultListener* listener) const { + decltype(auto) to = Caster::template Cast(from); + return MatchPrintAndExplain(to, this->matcher_, listener); + } + protected: const Matcher matcher_; @@ -2053,32 +2052,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 -class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase { +#if GTEST_HAS_RTTI +class DynamicCaster { public: - explicit WhenDynamicCastToMatcher(const Matcher& matcher) - : WhenDynamicCastToMatcherBase(matcher) {} + static constexpr const char* Name = "dynamic_cast"; - template - bool MatchAndExplain(From from, MatchResultListener* listener) const { - To to = dynamic_cast(from); - return MatchPrintAndExplain(to, this->matcher_, listener); + template + static To Cast(From&& from) { + return dynamic_cast(from); } }; +// Implements the WhenDynamicCastTo(m) matcher that matches a pointer or +// reference that matches inner_matcher when dynamic_cast is applied. +// The result of dynamic_cast is forwarded to the inner matcher. + +// To is a pointer. Cast and forward the result, which might be nullptr. +template +class WhenDynamicCastToMatcher + : public WhenCastToMatcherBase { + public: + using WhenCastToMatcherBase::WhenCastToMatcherBase; +}; + // Specialize for references. // In this case we return false if the dynamic_cast fails. template -class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase { +class WhenDynamicCastToMatcher + : public WhenCastToMatcherBase { public: - explicit WhenDynamicCastToMatcher(const Matcher& matcher) - : WhenDynamicCastToMatcherBase(matcher) {} + using WhenCastToMatcherBase::WhenCastToMatcherBase; template bool MatchAndExplain(From& from, MatchResultListener* listener) const { @@ -2093,6 +2100,25 @@ class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase { }; #endif // GTEST_HAS_RTTI +// Implements the WhenStaticCastTo(m) matcher that matches a pointer or +// reference that matches inner_matcher when static_cast is applied. +// The result of static_cast is forwarded to the inner matcher. +class StaticCaster { + public: + static constexpr const char* Name = "static_cast"; + + template + static To Cast(From&& from) { + return static_cast(from); + } +}; + +template +class WhenStaticCastToMatcher : public WhenCastToMatcherBase { + public: + using WhenCastToMatcherBase::WhenCastToMatcherBase; +}; + // Implements the Field() matcher for matching a field (i.e. member // variable) of an object. template @@ -4599,6 +4625,16 @@ WhenDynamicCastTo(const Matcher& inner_matcher) { } #endif // GTEST_HAS_RTTI +// Creates a matcher that matches a pointer or reference that matches +// inner_matcher when static_cast is applied. +// The result of static_cast is forwarded to the inner matcher. +template +inline PolymorphicMatcher> +WhenStaticCastTo(const Matcher& inner_matcher) { + return MakePolymorphicMatcher( + internal::WhenStaticCastToMatcher(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 a331aeca..73307a21 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(Pointee(FieldIIs(4)))); + EXPECT_THAT(as_void_ptr, + WhenStaticCastTo(Pointee(Not(FieldIIs(5))))); +} + +TEST(WhenStaticCastToTest, Inheritance) { + Derived derived; + EXPECT_THAT(derived, WhenStaticCastTo(_)); + EXPECT_THAT(&derived, WhenStaticCastTo(_)); + EXPECT_THAT(derived, WhenStaticCastTo(_)); + EXPECT_THAT(&derived, WhenStaticCastTo(_)); + // These will not compile because direct sidecasts are invalid + // EXPECT_THAT(derived, WhenStaticCastTo(_)); + // EXPECT_THAT(&derived, WhenStaticCastTo(_)); + + Base& as_base_ref = derived; + EXPECT_THAT(as_base_ref, WhenStaticCastTo(_)); + EXPECT_THAT(&as_base_ref, WhenStaticCastTo(_)); + EXPECT_THAT(as_base_ref, WhenStaticCastTo(_)); + EXPECT_THAT(&as_base_ref, WhenStaticCastTo(_)); + // These will compile and match, but are not safe + EXPECT_THAT(as_base_ref, WhenStaticCastTo(_)); + EXPECT_THAT(&as_base_ref, WhenStaticCastTo(_)); +} + +TEST(WhenStaticCastToTest, Punning) { + std::uint32_t u32 = 0xCCCCCCCC; + EXPECT_THAT(&u32, WhenStaticCastTo( + WhenStaticCastTo(Pointee(Eq(0xCC))))); +} + class DivisibleByImpl { public: explicit DivisibleByImpl(int a_divider) : divider_(a_divider) {}