From 285273473703fd39be769508dafb2af094b9b1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Dumas?= Date: Wed, 7 Feb 2024 08:20:49 -0800 Subject: [PATCH] Support compiling Happly without RTTI. --- happly.h | 158 +++++++++++++++++++++++++++++++++++++++++--- test/CMakeLists.txt | 19 +++++- test/other.cpp | 1 + 3 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 test/other.cpp diff --git a/happly.h b/happly.h index 4ee8c71..51cc50a 100644 --- a/happly.h +++ b/happly.h @@ -60,6 +60,14 @@ SOFTWARE. #include #include +#if __clang__ && !__INTEL_COMPILER + #define HAPPLY_ENABLE_RTTI __has_feature(cxx_rtti) +#elif defined(_CPPRTTI) + #define HAPPLY_ENABLE_RTTI 1 +#else + #define HAPPLY_ENABLE_RTTI (__GXX_RTTI || __RTTI || __INTEL_RTTI__) +#endif + // General namespace wrapping all Happly things. namespace happly { @@ -115,12 +123,96 @@ S* addressIfSame(S& t, int) {return &t;} // clang-format on } // namespace +#if !HAPPLY_ENABLE_RTTI + +template +class RTTIRootId { + +public: + virtual ~RTTIRootId() = default; + +public: + static constexpr char ID = 0; +}; + +template +constexpr char RTTIRootId::ID; + +/** + * @brief Base class for the extensible RTTI hierarchy. + * + * This class defines virtual methods, dynamicClassID and isA, that enable type comparisons. + */ +class RTTIRoot : public RTTIRootId<> { +public: + virtual ~RTTIRoot() = default; + + /** + * @brief Returns the class ID for this type. + */ + static const void *classID() { return &ID; } + + /** + * @brief Returns the class ID for the dynamic type of this RTTIRoot instance. + */ + virtual const void *dynamicClassID() const = 0; + + /** + * @brief Returns true if this class's ID matches the given class ID. + */ + virtual bool isA(const void *const ClassID) const { + return ClassID == classID(); + } + + /** + * @brief Check whether this instance is a subclass of QueryT. + */ + template + bool isIntanceOf() const { return isA(QueryT::classID()); } +}; + +/** + * @brief Inheritance utility for extensible RTTI. + * + * Supports single inheritance only: A class can only have one ExtensibleRTTI-parent (i.e. a parent + * for which the isa<> test will work), though it can have many non-ExtensibleRTTI parents. + * + * RTTIExtents uses CRTP so the first template argument to RTTIExtends is the newly introduced + * type, and the *second* argument is the parent class. + * + * class MyType : public RTTIExtends { public: static char ID; }; + * + * class MyDerivedType : public RTTIExtends { public: static char ID; }; + */ +template +class RTTIExtends : public ParentT { +public: + // Inherit constructors from ParentT. + using ParentT::ParentT; + + static const void *classID() { return &ThisT::ID; } + + const void *dynamicClassID() const override { return &ThisT::ID; } + + bool isA(const void *const ClassID) const override { + return ClassID == classID() || ParentT::isA(ClassID); + } + + static bool classof(const RTTIRoot *R) { return R->isIntanceOf(); } +}; + +#endif + /** * @brief A generic property, which is associated with some element. Can be plain Property or a ListProperty, of some * type. Generally, the user should not need to interact with these directly, but they are exposed in case someone * wants to get clever. */ +#if HAPPLY_ENABLE_RTTI class Property { +#else +class Property : public RTTIExtends { +#endif public: /** @@ -206,6 +298,28 @@ class Property { * @return */ virtual std::string propertyTypeName() = 0; + + /** + * @brief Attemps a dynamic cast to a derived class. + * + * @tparam DerivedPtr Derived class to cast to. + * + * @return A pointer to the derived class instance (if the cast is valid). A nullptr otherwise. + */ + template + typename std::add_pointer::type>::type>::type downcast() + { +#if HAPPLY_ENABLE_RTTI + using Derived = typename std::decay::type>::type; + return dynamic_cast(this); +#else + using Derived = typename std::decay::type>::type; + if (isIntanceOf()) { + return static_cast(this); + } + return nullptr; +#endif + } }; namespace { @@ -273,7 +387,16 @@ std::vector> unflattenList(const std::vector& flatList, const * @brief A property which takes a single value (not a list). */ template +#if HAPPLY_ENABLE_RTTI class TypedProperty : public Property { + using Super = Property; +#else +class TypedProperty : public RTTIExtends, Property> { + using Super = RTTIExtends, Property>; + +public: + static constexpr char ID = 0; +#endif public: /** @@ -281,7 +404,7 @@ class TypedProperty : public Property { * * @param name_ */ - TypedProperty(const std::string& name_) : Property(name_) { + TypedProperty(const std::string& name_) : Super(name_) { if (typeName() == "unknown") { // TODO should really be a compile-time error throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); @@ -294,7 +417,7 @@ class TypedProperty : public Property { * @param name_ * @param data_ */ - TypedProperty(const std::string& name_, const std::vector& data_) : Property(name_), data(data_) { + TypedProperty(const std::string& name_, const std::vector& data_) : Super(name_), data(data_) { if (typeName() == "unknown") { throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); } @@ -351,7 +474,7 @@ class TypedProperty : public Property { * @param outStream Stream to write to. */ virtual void writeHeader(std::ostream& outStream) override { - outStream << "property " << typeName() << " " << name << "\n"; + outStream << "property " << typeName() << " " << this->name << "\n"; } /** @@ -407,12 +530,25 @@ class TypedProperty : public Property { std::vector data; }; +#if !HAPPLY_ENABLE_RTTI +template +constexpr char TypedProperty::ID; +#endif /** * @brief A property which is a list of value (eg, 3 doubles). Note that lists are always variable length per-element. */ template +#if HAPPLY_ENABLE_RTTI class TypedListProperty : public Property { + using Super = Property; +#else +class TypedListProperty : public RTTIExtends, Property> { + using Super = RTTIExtends, Property>; + +public: + static constexpr char ID = 0; +#endif public: /** @@ -420,7 +556,7 @@ class TypedListProperty : public Property { * * @param name_ */ - TypedListProperty(const std::string& name_, int listCountBytes_) : Property(name_), listCountBytes(listCountBytes_) { + TypedListProperty(const std::string& name_, int listCountBytes_) : Super(name_), listCountBytes(listCountBytes_) { if (typeName() == "unknown") { throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); } @@ -434,7 +570,7 @@ class TypedListProperty : public Property { * @param name_ * @param data_ */ - TypedListProperty(const std::string& name_, const std::vector>& data_) : Property(name_) { + TypedListProperty(const std::string& name_, const std::vector>& data_) : Super(name_) { if (typeName() == "unknown") { throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); } @@ -548,7 +684,7 @@ class TypedListProperty : public Property { */ virtual void writeHeader(std::ostream& outStream) override { // NOTE: We ALWAYS use uchar as the list count output type - outStream << "property list uchar " << typeName() << " " << name << "\n"; + outStream << "property list uchar " << typeName() << " " << this->name << "\n"; } /** @@ -655,6 +791,10 @@ class TypedListProperty : public Property { int listCountBytes = -1; }; +#if !HAPPLY_ENABLE_RTTI +template +constexpr char TypedListProperty::ID; +#endif /** * @brief Helper function to construct a new property of the appropriate type. @@ -818,7 +958,7 @@ class Element { bool hasPropertyType(const std::string& target) { for (std::unique_ptr& prop : properties) { if (prop->name == target) { - TypedProperty* castedProp = dynamic_cast*>(prop.get()); + TypedProperty* castedProp = prop->downcast*>(); if (castedProp) { return true; } @@ -1157,7 +1297,7 @@ class Element { typedef typename CanonicalName::type Tcan; { // Try to return data of type D from a property of type T - TypedProperty* castedProp = dynamic_cast*>(prop); + TypedProperty* castedProp = prop->downcast*>(); if (castedProp) { // Succeeded, return a buffer of the data (copy while converting type) std::vector castedVec; @@ -1194,7 +1334,7 @@ class Element { std::vector> getDataFromListPropertyRecursive(Property* prop) { typedef typename CanonicalName::type Tcan; - TypedListProperty* castedProp = dynamic_cast*>(prop); + TypedListProperty* castedProp = prop->downcast*>(); if (castedProp) { // Succeeded, return a buffer of the data (copy while converting type) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ec9955e..6156392 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,7 +8,6 @@ cmake_policy(SET CMP0054 NEW) # don't implicitly dereference inside if() ### Configure output locations set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) ### Compile options @@ -85,13 +84,31 @@ set_property(TARGET gtest PROPERTY CXX_STANDARD 14) # Test executable add_executable(ply-test main_test.cpp + other.cpp ) target_link_libraries(ply-test gtest) target_include_directories(ply-test PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") +# Test executable (no-rtti) +add_executable(ply-test-no-rtti + main_test.cpp + other.cpp + ) + +target_link_libraries(ply-test-no-rtti gtest) + +target_include_directories(ply-test-no-rtti PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..") + +if(MSVC) + target_compile_options(ply-test-no-rtti PUBLIC /GR-) +else() + target_compile_options(ply-test-no-rtti PUBLIC -fno-rtti) +endif() + # Add cmake test target ("make test") enable_testing() add_test(MainTest ply-test) +add_test(MainTest-NoRTTI ply-test-no-rtti) diff --git a/test/other.cpp b/test/other.cpp new file mode 100644 index 0000000..903d700 --- /dev/null +++ b/test/other.cpp @@ -0,0 +1 @@ +#include "happly.h"