summaryrefslogtreecommitdiffstats
path: root/src/util/cast.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/cast.h101
1 files changed, 101 insertions, 0 deletions
diff --git a/src/util/cast.h b/src/util/cast.h
new file mode 100644
index 0000000..06fb088
--- /dev/null
+++ b/src/util/cast.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Hand-rolled LLVM-style RTTI system for class hierarchies where dynamic_cast isn't fast enough.
+ */
+#ifndef INKSCAPE_UTIL_CAST_H
+#define INKSCAPE_UTIL_CAST_H
+
+#include <type_traits>
+
+/*
+ * In order to use this system with a class hierarchy, specialize the following templates for
+ * every member of the hierarchy. The only requirement is that
+ *
+ * first_tag<T> <= first_tag<S> <= last_tag<T>
+ *
+ * exactly when S is a derived class of T. Then add to each class the line of boilerplate
+ *
+ * int tag() const override { return tag_of<decltype(*this)>; }
+ */
+template <typename T> inline constexpr int first_tag = std::enable_if<!sizeof(T), void>::value;
+template <typename T> inline constexpr int last_tag = std::enable_if<!sizeof(T), void>::value;
+
+/**
+ * Convenience function to retrieve the tag (class id) of a given type.
+ */
+template <typename T> inline constexpr int tag_of = first_tag<std::remove_cv_t<std::remove_reference_t<T>>>;
+
+/**
+ * Equivalent to the boolean value of dynamic_cast<T const*>(...).
+ *
+ * If the supplied pointer is null, the check fails.
+ *
+ * To help catch redundant checks, checks that are known at compile time currently generate
+ * a compile error. Please feel free to remove these static_asserts if they become unhelpful.
+ */
+template<typename T, typename S>
+bool is(S const *s)
+{
+ if (!s) return false;
+ if constexpr (std::is_base_of_v<T, S>) {
+ static_assert(!sizeof(T), "check is always true");
+ return true;
+ } else if constexpr (std::is_base_of_v<S, T>) {
+ auto const s_tag = s->tag();
+ return first_tag<T> <= s_tag && s_tag <= last_tag<T>;
+ } else {
+ static_assert(!sizeof(T), "check is always false");
+ return false;
+ }
+}
+
+/**
+ * Equivalent to static_cast<T [const]*>(...) where the const is deduced.
+ */
+template<typename T, typename S>
+auto cast_unsafe(S *s)
+{
+ return static_cast<T*>(s);
+}
+
+template<typename T, typename S>
+auto cast_unsafe(S const *s)
+{
+ return static_cast<T const*>(s);
+}
+
+/**
+ * Equivalent to dynamic_cast<T [const]*>(...) where the const is deduced.
+ *
+ * If the supplied pointer is null, the result is null.
+ *
+ * To help catch redundant casts, casts that are known at compile time currently generate
+ * a compile error. Please feel free to remove these static_asserts if they become unhelpful.
+ */
+template<typename T, typename S>
+auto cast(S *s)
+{
+ if constexpr (std::is_base_of_v<T, S>) {
+ // Removed static assert; it complicates template "collect_items"
+ // static_assert(!sizeof(T), "cast is unnecessary");
+ return cast_unsafe<T>(s);
+ } else if constexpr (std::is_base_of_v<S, T>) {
+ return is<T>(s) ? cast_unsafe<T>(s) : nullptr;
+ } else {
+ static_assert(!sizeof(T), "cast is impossible");
+ return nullptr;
+ }
+}
+
+#endif // INKSCAPE_UTIL_CAST_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :