Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support compiling Happly without RTTI. #41

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 144 additions & 12 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,102 @@ S* addressIfSame(S& t, int) {return &t;}
// clang-format on
} // namespace

#if HAPPLY_ENABLE_RTTI

class RTTIRoot {};

template <typename ThisT, typename ParentT>
class RTTIExtends : public ParentT {
// Inherit constructors from ParentT.
using ParentT::ParentT;
};

#else

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.
*/
class Property {
class Property : public RTTIExtends<Property, RTTIRoot> {

public:
/**
Expand Down Expand Up @@ -206,6 +304,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 +393,19 @@ 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>
class TypedProperty : public Property {
class TypedProperty : public RTTIExtends<TypedProperty<T>, Property> {
using Super = RTTIExtends<TypedProperty<T>, Property>;

public:
static constexpr char ID = 0;

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 +418,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 +475,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 +531,26 @@ class TypedProperty : public Property {
std::vector<T> data;
};

template <class T>
constexpr char TypedProperty<T>::ID;

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

public:
static constexpr char ID = 0;

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 +564,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 +678,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 +785,8 @@ class TypedListProperty : public Property {
int listCountBytes = -1;
};

template <class T>
constexpr char TypedListProperty<T>::ID;

/**
* @brief Helper function to construct a new property of the appropriate type.
Expand Down Expand Up @@ -818,7 +950,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 +1289,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 +1326,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"