Implement WhenStaticCastTo

This commit is contained in:
Dominik Kaszewski 2025-02-05 20:25:06 +01:00
parent e235eb34c6
commit 6ba68e583c
3 changed files with 104 additions and 24 deletions

View file

@ -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}

View file

@ -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))

View file

@ -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) {}