Skip to content

Commit

Permalink
Support compiling Happly without RTTI.
Browse files Browse the repository at this point in the history
  • Loading branch information
jdumas committed Feb 7, 2024
1 parent 8a60630 commit 2852734
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 10 deletions.
158 changes: 149 additions & 9 deletions happly.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ SOFTWARE.
#include <vector>
#include <climits>

#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 {

Expand Down Expand Up @@ -115,12 +123,96 @@ S* addressIfSame(S& t, int) {return &t;}
// clang-format on
} // namespace

#if !HAPPLY_ENABLE_RTTI

template<bool Dummy = true>
class RTTIRootId {

public:
virtual ~RTTIRootId() = default;

public:
static constexpr char ID = 0;
};

template<bool Dummy>
constexpr char RTTIRootId<Dummy>::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 <typename QueryT>
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<MyType, RTTIRoot> { public: static char ID; };
*
* class MyDerivedType : public RTTIExtends<MyDerivedType, MyType> { public: static char ID; };
*/
template <typename ThisT, typename ParentT>
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<ThisT>(); }
};

#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<Property, RTTIRoot> {
#endif

public:
/**
Expand Down Expand Up @@ -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 DerivedPtr>
typename std::add_pointer<typename std::decay<typename std::remove_pointer<DerivedPtr>::type>::type>::type downcast()
{
#if HAPPLY_ENABLE_RTTI
using Derived = typename std::decay<typename std::remove_pointer<DerivedPtr>::type>::type;
return dynamic_cast<Derived*>(this);
#else
using Derived = typename std::decay<typename std::remove_pointer<DerivedPtr>::type>::type;
if (isIntanceOf<Derived>()) {
return static_cast<Derived*>(this);
}
return nullptr;
#endif
}
};

namespace {
Expand Down Expand Up @@ -273,15 +387,24 @@ std::vector<std::vector<T>> unflattenList(const std::vector<T>& flatList, const
* @brief A property which takes a single value (not a list).
*/
template <class T>
#if HAPPLY_ENABLE_RTTI
class TypedProperty : public Property {
using Super = Property;
#else
class TypedProperty : public RTTIExtends<TypedProperty<T>, Property> {
using Super = RTTIExtends<TypedProperty<T>, Property>;

public:
static constexpr char ID = 0;
#endif

public:
/**
* @brief Create a new Property with the given name.
*
* @param name_
*/
TypedProperty(const std::string& name_) : Property(name_) {
TypedProperty(const std::string& name_) : Super(name_) {
if (typeName<T>() == "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.");
Expand All @@ -294,7 +417,7 @@ class TypedProperty : public Property {
* @param name_
* @param data_
*/
TypedProperty(const std::string& name_, const std::vector<T>& data_) : Property(name_), data(data_) {
TypedProperty(const std::string& name_, const std::vector<T>& data_) : Super(name_), data(data_) {
if (typeName<T>() == "unknown") {
throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
}
Expand Down Expand Up @@ -351,7 +474,7 @@ class TypedProperty : public Property {
* @param outStream Stream to write to.
*/
virtual void writeHeader(std::ostream& outStream) override {
outStream << "property " << typeName<T>() << " " << name << "\n";
outStream << "property " << typeName<T>() << " " << this->name << "\n";
}

/**
Expand Down Expand Up @@ -407,20 +530,33 @@ class TypedProperty : public Property {
std::vector<T> data;
};

#if !HAPPLY_ENABLE_RTTI
template <class T>
constexpr char TypedProperty<T>::ID;
#endif

/**
* @brief A property which is a list of value (eg, 3 doubles). Note that lists are always variable length per-element.
*/
template <class T>
#if HAPPLY_ENABLE_RTTI
class TypedListProperty : public Property {
using Super = Property;
#else
class TypedListProperty : public RTTIExtends<TypedListProperty<T>, Property> {
using Super = RTTIExtends<TypedListProperty<T>, Property>;

public:
static constexpr char ID = 0;
#endif

public:
/**
* @brief Create a new Property with the given name.
*
* @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<T>() == "unknown") {
throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
}
Expand All @@ -434,7 +570,7 @@ class TypedListProperty : public Property {
* @param name_
* @param data_
*/
TypedListProperty(const std::string& name_, const std::vector<std::vector<T>>& data_) : Property(name_) {
TypedListProperty(const std::string& name_, const std::vector<std::vector<T>>& data_) : Super(name_) {
if (typeName<T>() == "unknown") {
throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
}
Expand Down Expand Up @@ -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<T>() << " " << name << "\n";
outStream << "property list uchar " << typeName<T>() << " " << this->name << "\n";
}

/**
Expand Down Expand Up @@ -655,6 +791,10 @@ class TypedListProperty : public Property {
int listCountBytes = -1;
};

#if !HAPPLY_ENABLE_RTTI
template <class T>
constexpr char TypedListProperty<T>::ID;
#endif

/**
* @brief Helper function to construct a new property of the appropriate type.
Expand Down Expand Up @@ -818,7 +958,7 @@ class Element {
bool hasPropertyType(const std::string& target) {
for (std::unique_ptr<Property>& prop : properties) {
if (prop->name == target) {
TypedProperty<T>* castedProp = dynamic_cast<TypedProperty<T>*>(prop.get());
TypedProperty<T>* castedProp = prop->downcast<TypedProperty<T>*>();
if (castedProp) {
return true;
}
Expand Down Expand Up @@ -1157,7 +1297,7 @@ class Element {
typedef typename CanonicalName<T>::type Tcan;

{ // Try to return data of type D from a property of type T
TypedProperty<Tcan>* castedProp = dynamic_cast<TypedProperty<Tcan>*>(prop);
TypedProperty<Tcan>* castedProp = prop->downcast<TypedProperty<Tcan>*>();
if (castedProp) {
// Succeeded, return a buffer of the data (copy while converting type)
std::vector<D> castedVec;
Expand Down Expand Up @@ -1194,7 +1334,7 @@ class Element {
std::vector<std::vector<D>> getDataFromListPropertyRecursive(Property* prop) {
typedef typename CanonicalName<T>::type Tcan;

TypedListProperty<Tcan>* castedProp = dynamic_cast<TypedListProperty<Tcan>*>(prop);
TypedListProperty<Tcan>* castedProp = prop->downcast<TypedListProperty<Tcan>*>();
if (castedProp) {
// Succeeded, return a buffer of the data (copy while converting type)

Expand Down
19 changes: 18 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

1 change: 1 addition & 0 deletions test/other.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "happly.h"

0 comments on commit 2852734

Please sign in to comment.