Castable: Add FLAGS template argument for Is,As [Is,As] contain a static_assert() that checks a cast is actually possible (TO -> FROM share a common base class). This has been extremely valuable - it's caught numerious impossible casts due to stupid mistakes - however it makes certain generic templates impossible to write. Add a compile-time FLAGS argument to Is() and As(), which accepts a new kDontErrorOnImpossibleCast flag. When specified, this static_assert will always pass, allowing impossible casts to be attempted. Change-Id: I5ff434b329c04d007f4e6976301bf30c73ab3f8d Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47775 Reviewed-by: Antonio Maiorano <amaiorano@google.com> Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/castable.h b/src/castable.h index 460e509..884ba5f 100644 --- a/src/castable.h +++ b/src/castable.h
@@ -44,6 +44,9 @@ struct TypeInfoOf; } // namespace detail +// Forward declaration +class CastableBase; + /// Helper macro to instantiate the TypeInfo<T> template for `CLASS`. #define TINT_INSTANTIATE_TYPEINFO(CLASS) \ TINT_CASTABLE_PUSH_DISABLE_WARNINGS(); \ @@ -88,17 +91,36 @@ template <typename TO_FIRST, typename... TO_REST> struct IsAnyOf; +/// A placeholder structure used for template parameters that need a default +/// type, but can always be automatically inferred. +struct Infer; + } // namespace detail +/// Bit flags that can be passed to the template parameter `FLAGS` of Is() and +/// As(). +enum CastFlags { + /// Disables the static_assert() inside Is(), that compile-time-verifies that + /// the cast is possible. This flag may be useful for highly-generic template + /// code that needs to compile for template permutations that generate + /// impossible casts. + kDontErrorOnImpossibleCast = 1, +}; + /// @returns true if `obj` is a valid pointer, and is of, or derives from the /// class `TO` /// @param obj the object to test from -template <typename TO, typename FROM> +/// @see CastFlags +template <typename TO, int FLAGS = 0, typename FROM = detail::Infer> inline bool Is(FROM* obj) { constexpr const bool downcast = std::is_base_of<FROM, TO>::value; constexpr const bool upcast = std::is_base_of<TO, FROM>::value; constexpr const bool nocast = std::is_same<FROM, TO>::value; - static_assert(upcast || downcast || nocast, "impossible cast"); + constexpr const bool assert_is_castable = + (FLAGS & kDontErrorOnImpossibleCast) == 0; + + static_assert(upcast || downcast || nocast || !assert_is_castable, + "impossible cast"); if (obj == nullptr) { return false; @@ -116,7 +138,11 @@ /// @param obj the object to test from /// @param pred predicate function with signature `bool(const TO*)` called iff /// object is of, or derives from the class `TO`. -template <typename TO, typename FROM, typename Pred> +/// @see CastFlags +template <typename TO, + int FLAGS = 0, + typename FROM = detail::Infer, + typename Pred = detail::Infer> inline bool Is(FROM* obj, Pred&& pred) { constexpr const bool downcast = std::is_base_of<FROM, TO>::value; constexpr const bool upcast = std::is_base_of<TO, FROM>::value; @@ -144,9 +170,14 @@ /// @returns obj dynamically cast to the type `TO` or `nullptr` if /// this object does not derive from `TO`. /// @param obj the object to cast from -template <typename TO, typename FROM> +/// @see CastFlags +template <typename TO, int FLAGS = 0, typename FROM = detail::Infer> inline TO* As(FROM* obj) { - return Is<TO>(obj) ? static_cast<TO*>(obj) : nullptr; + using castable = + typename std::conditional<std::is_const<FROM>::value, const CastableBase, + CastableBase>::type; + auto* as_castable = static_cast<castable*>(obj); + return Is<TO, FLAGS>(obj) ? static_cast<TO*>(as_castable) : nullptr; } /// CastableBase is the base class for all Castable objects. @@ -173,9 +204,9 @@ /// pred(const TO*) returns true /// @param pred predicate function with signature `bool(const TO*)` called iff /// object is of, or derives from the class `TO`. - template <typename TO, typename Pred> + template <typename TO, int FLAGS = 0, typename Pred = detail::Infer> inline bool Is(Pred&& pred) const { - return tint::Is<TO>(this, std::forward<Pred>(pred)); + return tint::Is<TO, FLAGS>(this, std::forward<Pred>(pred)); } /// @returns true if this object is of, or derives from any of the `TO` @@ -187,16 +218,18 @@ /// @returns this object dynamically cast to the type `TO` or `nullptr` if /// this object does not derive from `TO`. - template <typename TO> + /// @see CastFlags + template <typename TO, int FLAGS = 0> inline TO* As() { - return tint::As<TO>(this); + return tint::As<TO, FLAGS>(this); } /// @returns this object dynamically cast to the type `TO` or `nullptr` if /// this object does not derive from `TO`. - template <typename TO> + /// @see CastFlags + template <typename TO, int FLAGS = 0> inline const TO* As() const { - return tint::As<const TO>(this); + return tint::As<const TO, FLAGS>(this); } protected: @@ -242,19 +275,20 @@ } /// @returns true if this object is of, or derives from the class `TO` - template <typename TO> + /// @see CastFlags + template <typename TO, int FLAGS = 0> inline bool Is() const { - return tint::Is<TO>(static_cast<const CLASS*>(this)); + return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this)); } /// @returns true if this object is of, or derives from the class `TO` and /// pred(const TO*) returns true /// @param pred predicate function with signature `bool(const TO*)` called iff /// object is of, or derives from the class `TO`. - template <typename TO, typename Pred> + template <typename TO, int FLAGS = 0, typename Pred = detail::Infer> inline bool Is(Pred&& pred) const { - return tint::Is<TO>(static_cast<const CLASS*>(this), - std::forward<Pred>(pred)); + return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this), + std::forward<Pred>(pred)); } /// @returns true if this object is of, or derives from any of the `TO` @@ -266,16 +300,18 @@ /// @returns this object dynamically cast to the type `TO` or `nullptr` if /// this object does not derive from `TO`. - template <typename TO> + /// @see CastFlags + template <typename TO, int FLAGS = 0> inline TO* As() { - return tint::As<TO>(this); + return tint::As<TO, FLAGS>(this); } /// @returns this object dynamically cast to the type `TO` or `nullptr` if /// this object does not derive from `TO`. - template <typename TO> + /// @see CastFlags + template <typename TO, int FLAGS = 0> inline const TO* As() const { - return tint::As<const TO>(this); + return tint::As<const TO, FLAGS>(this); } };
diff --git a/src/castable_test.cc b/src/castable_test.cc index a62bc3d..0d10aac 100644 --- a/src/castable_test.cc +++ b/src/castable_test.cc
@@ -73,6 +73,30 @@ ASSERT_TRUE(gecko->Is<Reptile>()); } +TEST(CastableBase, Is_kDontErrorOnImpossibleCast) { + // Unlike TEST(CastableBase, Is), we're dynamically querying [A -> B] without + // going via CastableBase. + auto frog = std::make_unique<Frog>(); + auto bear = std::make_unique<Bear>(); + auto gecko = std::make_unique<Gecko>(); + + ASSERT_TRUE((frog->Is<Animal, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((bear->Is<Animal, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((gecko->Is<Animal, kDontErrorOnImpossibleCast>())); + + ASSERT_TRUE((frog->Is<Amphibian, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((bear->Is<Amphibian, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((gecko->Is<Amphibian, kDontErrorOnImpossibleCast>())); + + ASSERT_FALSE((frog->Is<Mammal, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((bear->Is<Mammal, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((gecko->Is<Mammal, kDontErrorOnImpossibleCast>())); + + ASSERT_FALSE((frog->Is<Reptile, kDontErrorOnImpossibleCast>())); + ASSERT_FALSE((bear->Is<Reptile, kDontErrorOnImpossibleCast>())); + ASSERT_TRUE((gecko->Is<Reptile, kDontErrorOnImpossibleCast>())); +} + TEST(CastableBase, IsWithPredicate) { std::unique_ptr<CastableBase> frog = std::make_unique<Frog>(); @@ -135,6 +159,36 @@ ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get())); } +TEST(CastableBase, As_kDontErrorOnImpossibleCast) { + // Unlike TEST(CastableBase, As), we're dynamically casting [A -> B] without + // going via CastableBase. + auto frog = std::make_unique<Frog>(); + auto bear = std::make_unique<Bear>(); + auto gecko = std::make_unique<Gecko>(); + + ASSERT_EQ((frog->As<Animal, kDontErrorOnImpossibleCast>()), + static_cast<Animal*>(frog.get())); + ASSERT_EQ((bear->As<Animal, kDontErrorOnImpossibleCast>()), + static_cast<Animal*>(bear.get())); + ASSERT_EQ((gecko->As<Animal, kDontErrorOnImpossibleCast>()), + static_cast<Animal*>(gecko.get())); + + ASSERT_EQ((frog->As<Amphibian, kDontErrorOnImpossibleCast>()), + static_cast<Amphibian*>(frog.get())); + ASSERT_EQ((bear->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((gecko->As<Amphibian, kDontErrorOnImpossibleCast>()), nullptr); + + ASSERT_EQ((frog->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((bear->As<Mammal, kDontErrorOnImpossibleCast>()), + static_cast<Mammal*>(bear.get())); + ASSERT_EQ((gecko->As<Mammal, kDontErrorOnImpossibleCast>()), nullptr); + + ASSERT_EQ((frog->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((bear->As<Reptile, kDontErrorOnImpossibleCast>()), nullptr); + ASSERT_EQ((gecko->As<Reptile, kDontErrorOnImpossibleCast>()), + static_cast<Reptile*>(gecko.get())); +} + TEST(Castable, Is) { std::unique_ptr<Animal> frog = std::make_unique<Frog>(); std::unique_ptr<Animal> bear = std::make_unique<Bear>();