Add Castable template class
Implements `As<Blah>()` and `Is<Blah>()` 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<T>()` 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<Blah>()) {
...
}
```
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 <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index cd9c72a..af036e5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -387,6 +387,8 @@
"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 @@
"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 45b2c88..b5c4477 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -208,6 +208,8 @@
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 @@
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 0000000..0ddfdb2
--- /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<CastableBase>() == id;
+}
+
+} // namespace tint
diff --git a/src/castable.h b/src/castable.h
new file mode 100644
index 0000000..165b6fc
--- /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 <cstdint>
+#include <type_traits>
+#include <utility>
+
+namespace tint {
+
+/// ClassID represents a unique, comparable identifier for a C++ type.
+class ClassID {
+ public:
+ /// @returns the unique ClassID for the type T.
+ template <typename T>
+ static ClassID Of() {
+ static char _;
+ return ClassID{reinterpret_cast<uintptr_t>(&_)};
+ }
+
+ /// 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 <typename TO>
+ bool Is() const {
+ using FROM = CastableBase;
+ 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");
+
+ if (upcast || nocast) {
+ return true;
+ }
+
+ return Is(ClassID::Of<TO>());
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+ /// this object does not derive from `TO`.
+ template <typename TO>
+ inline TO* As() {
+ return Is<TO>() ? static_cast<TO*>(this) : nullptr;
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+ /// this object does not derive from `TO`.
+ template <typename TO>
+ inline const TO* As() const {
+ return Is<TO>() ? static_cast<const TO*>(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<Animal> {};
+///
+/// class Sheep : public Castable<Sheep, Animal> {};
+///
+/// Sheep* cast_to_sheep(Animal* animal) {
+/// // You can query whether a Castable is of the given type with Is<T>():
+/// printf("animal is a sheep? %s", animal->Is<Sheep>() ? "yes" : "no");
+///
+/// // You can always just try the cast with As<T>().
+/// // If the object is not of the correct type, As<T>() will return nullptr:
+/// return animal->As<Sheep>();
+/// }
+/// ```
+template <typename CLASS, typename BASE = CastableBase>
+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<CLASS>() == id || BASE::Is(id);
+ }
+
+ /// @returns true if this object is of, or derives from the class `TO`
+ template <typename TO>
+ bool Is() const {
+ using FROM = Castable;
+ 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");
+
+ if (upcast || nocast) {
+ return true;
+ }
+
+ return Is(ClassID::Of<TO>());
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+ /// this object does not derive from `TO`.
+ template <typename TO>
+ inline TO* As() {
+ return Is<TO>() ? static_cast<TO*>(this) : nullptr;
+ }
+
+ /// @returns this object dynamically cast to the type `TO` or `nullptr` if
+ /// this object does not derive from `TO`.
+ template <typename TO>
+ inline const TO* As() const {
+ return Is<TO>() ? static_cast<const TO*>(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 <typename TO, typename FROM>
+inline TO* As(FROM* obj) {
+ if (obj == nullptr) {
+ return nullptr;
+ }
+ return obj->template As<TO>();
+}
+
+} // namespace tint
+
+#endif // SRC_CASTABLE_H_
diff --git a/src/castable_test.cc b/src/castable_test.cc
new file mode 100644
index 0000000..5c7a9bd
--- /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 <memory>
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace tint {
+namespace {
+
+struct Animal : public Castable<Animal> {
+ explicit Animal(std::string n) : name(n) {}
+ const std::string name;
+};
+
+struct Amphibian : public Castable<Amphibian, Animal> {
+ explicit Amphibian(std::string n) : Base(n) {}
+};
+
+struct Mammal : public Castable<Mammal, Animal> {
+ explicit Mammal(std::string n) : Base(n) {}
+};
+
+struct Reptile : public Castable<Reptile, Animal> {
+ explicit Reptile(std::string n) : Base(n) {}
+};
+
+struct Frog : public Castable<Frog, Amphibian> {
+ Frog() : Base("Frog") {}
+};
+
+struct Bear : public Castable<Bear, Mammal> {
+ Bear() : Base("Bear") {}
+};
+
+struct Gecko : public Castable<Gecko, Reptile> {
+ Gecko() : Base("Gecko") {}
+};
+
+} // namespace
+
+TEST(CastableBase, Is) {
+ std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+ std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+ std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+ ASSERT_TRUE(frog->Is<Animal>());
+ ASSERT_TRUE(bear->Is<Animal>());
+ ASSERT_TRUE(gecko->Is<Animal>());
+
+ ASSERT_TRUE(frog->Is<Amphibian>());
+ ASSERT_FALSE(bear->Is<Amphibian>());
+ ASSERT_FALSE(gecko->Is<Amphibian>());
+
+ ASSERT_FALSE(frog->Is<Mammal>());
+ ASSERT_TRUE(bear->Is<Mammal>());
+ ASSERT_FALSE(gecko->Is<Mammal>());
+
+ ASSERT_FALSE(frog->Is<Reptile>());
+ ASSERT_FALSE(bear->Is<Reptile>());
+ ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(CastableBase, As) {
+ std::unique_ptr<CastableBase> frog = std::make_unique<Frog>();
+ std::unique_ptr<CastableBase> bear = std::make_unique<Bear>();
+ std::unique_ptr<CastableBase> gecko = std::make_unique<Gecko>();
+
+ ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+ ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+ ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+ ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+ ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+ ASSERT_EQ(frog->As<Mammal>(), nullptr);
+ ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+ ASSERT_EQ(frog->As<Reptile>(), nullptr);
+ ASSERT_EQ(bear->As<Reptile>(), nullptr);
+ ASSERT_EQ(gecko->As<Reptile>(), 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>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+ ASSERT_TRUE(frog->Is<Animal>());
+ ASSERT_TRUE(bear->Is<Animal>());
+ ASSERT_TRUE(gecko->Is<Animal>());
+
+ ASSERT_TRUE(frog->Is<Amphibian>());
+ ASSERT_FALSE(bear->Is<Amphibian>());
+ ASSERT_FALSE(gecko->Is<Amphibian>());
+
+ ASSERT_FALSE(frog->Is<Mammal>());
+ ASSERT_TRUE(bear->Is<Mammal>());
+ ASSERT_FALSE(gecko->Is<Mammal>());
+
+ ASSERT_FALSE(frog->Is<Reptile>());
+ ASSERT_FALSE(bear->Is<Reptile>());
+ ASSERT_TRUE(gecko->Is<Reptile>());
+}
+
+TEST(Castable, As) {
+ std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+ std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+ std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+
+ ASSERT_EQ(frog->As<Animal>(), static_cast<Animal*>(frog.get()));
+ ASSERT_EQ(bear->As<Animal>(), static_cast<Animal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Animal>(), static_cast<Animal*>(gecko.get()));
+
+ ASSERT_EQ(frog->As<Amphibian>(), static_cast<Amphibian*>(frog.get()));
+ ASSERT_EQ(bear->As<Amphibian>(), nullptr);
+ ASSERT_EQ(gecko->As<Amphibian>(), nullptr);
+
+ ASSERT_EQ(frog->As<Mammal>(), nullptr);
+ ASSERT_EQ(bear->As<Mammal>(), static_cast<Mammal*>(bear.get()));
+ ASSERT_EQ(gecko->As<Mammal>(), nullptr);
+
+ ASSERT_EQ(frog->As<Reptile>(), nullptr);
+ ASSERT_EQ(bear->As<Reptile>(), nullptr);
+ ASSERT_EQ(gecko->As<Reptile>(), static_cast<Reptile*>(gecko.get()));
+}
+
+} // namespace tint