From 95feb99a9d65641ee022ad7fa087be27a3ed4b84 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Mon, 30 Nov 2020 22:50:25 +0000 Subject: [PATCH] Add Castable template class Implements `As()` and `Is()` automatically. There are several benefits to using this over the pattern of hand-rolled `IsBlah()`, `AsBlah()` methods: (1) We don't have to maintain a whole lot of hand written code. (2) These allow us to cast from the base type to _any_ derived type in a single cast. The existing hand-rolled methods usually require a couple of intermediary casts to go from the base type to the leaf type. (3) The use of a template parameter means these casts can be called from other template logic. Note: Unlike the hand-rolled `AsBlah()` methods, it is safe to call `As()` even if the type does not derive from `T`. If the object does not derive from `T` then `As` will simply return `nullptr`. This allows the calling logic to replace the common pattern of: ``` if (obj.IsBlah()) { auto* b = obj.AsBlah(); ... } ``` with: ``` if (auto* b = obj.As()) { ... } ``` This halves the number of virtual method calls, and is one line shorter. Change-Id: I4312e9831d7de6703a97184640864b8050a34177 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/34260 Reviewed-by: dan sinclair Commit-Queue: Ben Clayton --- BUILD.gn | 3 + src/CMakeLists.txt | 3 + src/castable.cc | 23 ++++++ src/castable.h | 174 +++++++++++++++++++++++++++++++++++++++++++ src/castable_test.cc | 144 +++++++++++++++++++++++++++++++++++ 5 files changed, 347 insertions(+) create mode 100644 src/castable.cc create mode 100644 src/castable.h create mode 100644 src/castable_test.cc diff --git a/BUILD.gn b/BUILD.gn index cd9c72a9eb..af036e5bb2 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -387,6 +387,8 @@ source_set("libtint_core_src") { "src/ast/variable_decoration.h", "src/ast/workgroup_decoration.cc", "src/ast/workgroup_decoration.h", + "src/castable.cc", + "src/castable.h", "src/context.cc", "src/context.h", "src/diagnostic/diagnostic.cc", @@ -805,6 +807,7 @@ source_set("tint_unittests_core_src") { "src/ast/variable_decl_statement_test.cc", "src/ast/variable_test.cc", "src/ast/workgroup_decoration_test.cc", + "src/castable_test.cc", "src/diagnostic/formatter_test.cc", "src/diagnostic/printer_test.cc", "src/inspector/inspector_test.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45b2c88be0..b5c44775cf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -208,6 +208,8 @@ set(TINT_LIB_SRCS ast/variable_decl_statement.h ast/workgroup_decoration.cc ast/workgroup_decoration.h + castable.cc + castable.h context.cc context.h diagnostic/diagnostic.cc @@ -415,6 +417,7 @@ set(TINT_TEST_SRCS ast/variable_decl_statement_test.cc ast/variable_test.cc ast/workgroup_decoration_test.cc + castable_test.cc diagnostic/formatter_test.cc diagnostic/printer_test.cc inspector/inspector_test.cc diff --git a/src/castable.cc b/src/castable.cc new file mode 100644 index 0000000000..0ddfdb2fd7 --- /dev/null +++ b/src/castable.cc @@ -0,0 +1,23 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/castable.h" + +namespace tint { + +bool CastableBase::Is(ClassID id) const { + return ClassID::Of() == id; +} + +} // namespace tint diff --git a/src/castable.h b/src/castable.h new file mode 100644 index 0000000000..165b6fcf6e --- /dev/null +++ b/src/castable.h @@ -0,0 +1,174 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SRC_CASTABLE_H_ +#define SRC_CASTABLE_H_ + +#include +#include +#include + +namespace tint { + +/// ClassID represents a unique, comparable identifier for a C++ type. +class ClassID { + public: + /// @returns the unique ClassID for the type T. + template + static ClassID Of() { + static char _; + return ClassID{reinterpret_cast(&_)}; + } + + /// Equality operator + /// @param rhs the ClassID to compare against + /// @returns true if this ClassID is equal to @p rhs + inline bool operator==(ClassID& rhs) const { return id == rhs.id; } + + private: + inline explicit ClassID(uintptr_t v) : id(v) {} + + const uintptr_t id; +}; + +/// CastableBase is the base class for all Castable objects. +/// It is not encouraged to directly derive from CastableBase without using the +/// Castable helper template. +/// @see Castable +class CastableBase { + public: + virtual ~CastableBase() = default; + + /// @returns true if this object is of, or derives from a class with the + /// ClassID @p id. + /// @param id the ClassID to test for + virtual bool Is(ClassID id) const; + + /// @returns true if this object is of, or derives from the class `TO` + template + bool Is() const { + using FROM = CastableBase; + constexpr const bool downcast = std::is_base_of::value; + constexpr const bool upcast = std::is_base_of::value; + constexpr const bool nocast = std::is_same::value; + static_assert(upcast || downcast || nocast, "impossible cast"); + + if (upcast || nocast) { + return true; + } + + return Is(ClassID::Of()); + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + template + inline TO* As() { + return Is() ? static_cast(this) : nullptr; + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + template + inline const TO* As() const { + return Is() ? static_cast(this) : nullptr; + } + + protected: + CastableBase() = default; +}; + +/// Castable is a helper to derive `CLASS` from `BASE`, automatically +/// implementing the Is() and As() methods, along with a #Base type alias. +/// +/// Example usage: +/// +/// ``` +/// class Animal : public Castable {}; +/// +/// class Sheep : public Castable {}; +/// +/// Sheep* cast_to_sheep(Animal* animal) { +/// // You can query whether a Castable is of the given type with Is(): +/// printf("animal is a sheep? %s", animal->Is() ? "yes" : "no"); +/// +/// // You can always just try the cast with As(). +/// // If the object is not of the correct type, As() will return nullptr: +/// return animal->As(); +/// } +/// ``` +template +class Castable : public BASE { + public: + // Inherit the `BASE` class constructors. + using BASE::BASE; + + /// A type alias for `CLASS` to easily access the `BASE` class members. + /// Base actually aliases to the Castable instead of `BASE` so that you can + /// use Base in the `CLASS` constructor. + using Base = Castable; + + /// @returns true if this object is of, or derives from a class with the + /// ClassID @p id. + /// @param id the ClassID to test for + bool Is(ClassID id) const override { + return ClassID::Of() == id || BASE::Is(id); + } + + /// @returns true if this object is of, or derives from the class `TO` + template + bool Is() const { + using FROM = Castable; + constexpr const bool downcast = std::is_base_of::value; + constexpr const bool upcast = std::is_base_of::value; + constexpr const bool nocast = std::is_same::value; + static_assert(upcast || downcast || nocast, "impossible cast"); + + if (upcast || nocast) { + return true; + } + + return Is(ClassID::Of()); + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + template + inline TO* As() { + return Is() ? static_cast(this) : nullptr; + } + + /// @returns this object dynamically cast to the type `TO` or `nullptr` if + /// this object does not derive from `TO`. + template + inline const TO* As() const { + return Is() ? static_cast(this) : nullptr; + } +}; + +/// As() dynamically casts @p obj to the target type `TO`. +/// @returns the cast object, or nullptr if @p obj is `nullptr` or not of the +/// type `TO`. +/// @param obj the object to cast +template +inline TO* As(FROM* obj) { + if (obj == nullptr) { + return nullptr; + } + return obj->template As(); +} + +} // namespace tint + +#endif // SRC_CASTABLE_H_ diff --git a/src/castable_test.cc b/src/castable_test.cc new file mode 100644 index 0000000000..5c7a9bd3ac --- /dev/null +++ b/src/castable_test.cc @@ -0,0 +1,144 @@ +// Copyright 2020 The Tint Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/castable.h" + +#include +#include + +#include "gtest/gtest.h" + +namespace tint { +namespace { + +struct Animal : public Castable { + explicit Animal(std::string n) : name(n) {} + const std::string name; +}; + +struct Amphibian : public Castable { + explicit Amphibian(std::string n) : Base(n) {} +}; + +struct Mammal : public Castable { + explicit Mammal(std::string n) : Base(n) {} +}; + +struct Reptile : public Castable { + explicit Reptile(std::string n) : Base(n) {} +}; + +struct Frog : public Castable { + Frog() : Base("Frog") {} +}; + +struct Bear : public Castable { + Bear() : Base("Bear") {} +}; + +struct Gecko : public Castable { + Gecko() : Base("Gecko") {} +}; + +} // namespace + +TEST(CastableBase, Is) { + std::unique_ptr frog = std::make_unique(); + std::unique_ptr bear = std::make_unique(); + std::unique_ptr gecko = std::make_unique(); + + ASSERT_TRUE(frog->Is()); + ASSERT_TRUE(bear->Is()); + ASSERT_TRUE(gecko->Is()); + + ASSERT_TRUE(frog->Is()); + ASSERT_FALSE(bear->Is()); + ASSERT_FALSE(gecko->Is()); + + ASSERT_FALSE(frog->Is()); + ASSERT_TRUE(bear->Is()); + ASSERT_FALSE(gecko->Is()); + + ASSERT_FALSE(frog->Is()); + ASSERT_FALSE(bear->Is()); + ASSERT_TRUE(gecko->Is()); +} + +TEST(CastableBase, As) { + std::unique_ptr frog = std::make_unique(); + std::unique_ptr bear = std::make_unique(); + std::unique_ptr gecko = std::make_unique(); + + ASSERT_EQ(frog->As(), static_cast(frog.get())); + ASSERT_EQ(bear->As(), static_cast(bear.get())); + ASSERT_EQ(gecko->As(), static_cast(gecko.get())); + + ASSERT_EQ(frog->As(), static_cast(frog.get())); + ASSERT_EQ(bear->As(), nullptr); + ASSERT_EQ(gecko->As(), nullptr); + + ASSERT_EQ(frog->As(), nullptr); + ASSERT_EQ(bear->As(), static_cast(bear.get())); + ASSERT_EQ(gecko->As(), nullptr); + + ASSERT_EQ(frog->As(), nullptr); + ASSERT_EQ(bear->As(), nullptr); + ASSERT_EQ(gecko->As(), static_cast(gecko.get())); +} + +TEST(Castable, Is) { + std::unique_ptr frog = std::make_unique(); + std::unique_ptr bear = std::make_unique(); + std::unique_ptr gecko = std::make_unique(); + + ASSERT_TRUE(frog->Is()); + ASSERT_TRUE(bear->Is()); + ASSERT_TRUE(gecko->Is()); + + ASSERT_TRUE(frog->Is()); + ASSERT_FALSE(bear->Is()); + ASSERT_FALSE(gecko->Is()); + + ASSERT_FALSE(frog->Is()); + ASSERT_TRUE(bear->Is()); + ASSERT_FALSE(gecko->Is()); + + ASSERT_FALSE(frog->Is()); + ASSERT_FALSE(bear->Is()); + ASSERT_TRUE(gecko->Is()); +} + +TEST(Castable, As) { + std::unique_ptr frog = std::make_unique(); + std::unique_ptr bear = std::make_unique(); + std::unique_ptr gecko = std::make_unique(); + + ASSERT_EQ(frog->As(), static_cast(frog.get())); + ASSERT_EQ(bear->As(), static_cast(bear.get())); + ASSERT_EQ(gecko->As(), static_cast(gecko.get())); + + ASSERT_EQ(frog->As(), static_cast(frog.get())); + ASSERT_EQ(bear->As(), nullptr); + ASSERT_EQ(gecko->As(), nullptr); + + ASSERT_EQ(frog->As(), nullptr); + ASSERT_EQ(bear->As(), static_cast(bear.get())); + ASSERT_EQ(gecko->As(), nullptr); + + ASSERT_EQ(frog->As(), nullptr); + ASSERT_EQ(bear->As(), nullptr); + ASSERT_EQ(gecko->As(), static_cast(gecko.get())); +} + +} // namespace tint