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>
This commit is contained in:
parent
7687ec1fa3
commit
26cd48603d
|
@ -44,6 +44,9 @@ template <typename T>
|
||||||
struct TypeInfoOf;
|
struct TypeInfoOf;
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
class CastableBase;
|
||||||
|
|
||||||
/// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
|
/// Helper macro to instantiate the TypeInfo<T> template for `CLASS`.
|
||||||
#define TINT_INSTANTIATE_TYPEINFO(CLASS) \
|
#define TINT_INSTANTIATE_TYPEINFO(CLASS) \
|
||||||
TINT_CASTABLE_PUSH_DISABLE_WARNINGS(); \
|
TINT_CASTABLE_PUSH_DISABLE_WARNINGS(); \
|
||||||
|
@ -88,17 +91,36 @@ struct TypeInfoOf {
|
||||||
template <typename TO_FIRST, typename... TO_REST>
|
template <typename TO_FIRST, typename... TO_REST>
|
||||||
struct IsAnyOf;
|
struct IsAnyOf;
|
||||||
|
|
||||||
|
/// A placeholder structure used for template parameters that need a default
|
||||||
|
/// type, but can always be automatically inferred.
|
||||||
|
struct Infer;
|
||||||
|
|
||||||
} // namespace detail
|
} // 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
|
/// @returns true if `obj` is a valid pointer, and is of, or derives from the
|
||||||
/// class `TO`
|
/// class `TO`
|
||||||
/// @param obj the object to test from
|
/// @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) {
|
inline bool Is(FROM* obj) {
|
||||||
constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
|
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 upcast = std::is_base_of<TO, FROM>::value;
|
||||||
constexpr const bool nocast = std::is_same<FROM, TO>::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) {
|
if (obj == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -116,7 +138,11 @@ inline bool Is(FROM* obj) {
|
||||||
/// @param obj the object to test from
|
/// @param obj the object to test from
|
||||||
/// @param pred predicate function with signature `bool(const TO*)` called iff
|
/// @param pred predicate function with signature `bool(const TO*)` called iff
|
||||||
/// object is of, or derives from the class `TO`.
|
/// 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) {
|
inline bool Is(FROM* obj, Pred&& pred) {
|
||||||
constexpr const bool downcast = std::is_base_of<FROM, TO>::value;
|
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 upcast = std::is_base_of<TO, FROM>::value;
|
||||||
|
@ -144,9 +170,14 @@ inline bool IsAnyOf(FROM* obj) {
|
||||||
/// @returns obj dynamically cast to the type `TO` or `nullptr` if
|
/// @returns obj dynamically cast to the type `TO` or `nullptr` if
|
||||||
/// this object does not derive from `TO`.
|
/// this object does not derive from `TO`.
|
||||||
/// @param obj the object to cast from
|
/// @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) {
|
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.
|
/// CastableBase is the base class for all Castable objects.
|
||||||
|
@ -173,9 +204,9 @@ class CastableBase {
|
||||||
/// pred(const TO*) returns true
|
/// pred(const TO*) returns true
|
||||||
/// @param pred predicate function with signature `bool(const TO*)` called iff
|
/// @param pred predicate function with signature `bool(const TO*)` called iff
|
||||||
/// object is of, or derives from the class `TO`.
|
/// 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 {
|
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`
|
/// @returns true if this object is of, or derives from any of the `TO`
|
||||||
|
@ -187,16 +218,18 @@ class CastableBase {
|
||||||
|
|
||||||
/// @returns this object dynamically cast to the type `TO` or `nullptr` if
|
/// @returns this object dynamically cast to the type `TO` or `nullptr` if
|
||||||
/// this object does not derive from `TO`.
|
/// this object does not derive from `TO`.
|
||||||
template <typename TO>
|
/// @see CastFlags
|
||||||
|
template <typename TO, int FLAGS = 0>
|
||||||
inline TO* As() {
|
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
|
/// @returns this object dynamically cast to the type `TO` or `nullptr` if
|
||||||
/// this object does not derive from `TO`.
|
/// this object does not derive from `TO`.
|
||||||
template <typename TO>
|
/// @see CastFlags
|
||||||
|
template <typename TO, int FLAGS = 0>
|
||||||
inline const TO* As() const {
|
inline const TO* As() const {
|
||||||
return tint::As<const TO>(this);
|
return tint::As<const TO, FLAGS>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -242,19 +275,20 @@ class Castable : public BASE {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns true if this object is of, or derives from the class `TO`
|
/// @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 {
|
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
|
/// @returns true if this object is of, or derives from the class `TO` and
|
||||||
/// pred(const TO*) returns true
|
/// pred(const TO*) returns true
|
||||||
/// @param pred predicate function with signature `bool(const TO*)` called iff
|
/// @param pred predicate function with signature `bool(const TO*)` called iff
|
||||||
/// object is of, or derives from the class `TO`.
|
/// 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 {
|
inline bool Is(Pred&& pred) const {
|
||||||
return tint::Is<TO>(static_cast<const CLASS*>(this),
|
return tint::Is<TO, FLAGS>(static_cast<const CLASS*>(this),
|
||||||
std::forward<Pred>(pred));
|
std::forward<Pred>(pred));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @returns true if this object is of, or derives from any of the `TO`
|
/// @returns true if this object is of, or derives from any of the `TO`
|
||||||
|
@ -266,16 +300,18 @@ class Castable : public BASE {
|
||||||
|
|
||||||
/// @returns this object dynamically cast to the type `TO` or `nullptr` if
|
/// @returns this object dynamically cast to the type `TO` or `nullptr` if
|
||||||
/// this object does not derive from `TO`.
|
/// this object does not derive from `TO`.
|
||||||
template <typename TO>
|
/// @see CastFlags
|
||||||
|
template <typename TO, int FLAGS = 0>
|
||||||
inline TO* As() {
|
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
|
/// @returns this object dynamically cast to the type `TO` or `nullptr` if
|
||||||
/// this object does not derive from `TO`.
|
/// this object does not derive from `TO`.
|
||||||
template <typename TO>
|
/// @see CastFlags
|
||||||
|
template <typename TO, int FLAGS = 0>
|
||||||
inline const TO* As() const {
|
inline const TO* As() const {
|
||||||
return tint::As<const TO>(this);
|
return tint::As<const TO, FLAGS>(this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,30 @@ TEST(CastableBase, Is) {
|
||||||
ASSERT_TRUE(gecko->Is<Reptile>());
|
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) {
|
TEST(CastableBase, IsWithPredicate) {
|
||||||
std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
|
std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
|
||||||
|
|
||||||
|
@ -135,6 +159,36 @@ TEST(CastableBase, As) {
|
||||||
ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
|
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) {
|
TEST(Castable, Is) {
|
||||||
std::unique_ptr<Animal> frog = std::make_unique<Frog>();
|
std::unique_ptr<Animal> frog = std::make_unique<Frog>();
|
||||||
std::unique_ptr<Animal> bear = std::make_unique<Bear>();
|
std::unique_ptr<Animal> bear = std::make_unique<Bear>();
|
||||||
|
|
Loading…
Reference in New Issue