As far as I know, there are three situations where the type of an object is looked up at runtime: with dynamic_cast
, binding exception objects to handlers and looking up which virtual function to call.
Here's an implementation of using exception handlers to store type information at runtime: https://godbolt.org/z/Mf71bbr7P
#include <type_traits>
#include <typeinfo>
namespace detail {
template<typename T>
struct CType;
}
struct Type {
const std::type_info& type_info;
template<typename T>
friend struct detail::CType;
template<typename T>
friend bool IsOrInheritsFrom(const T&, const Type& type) noexcept {
return type.can_catch_thrown_pointer([]{ throw static_cast<T*>(nullptr); });
}
Type(const Type&) = delete;
Type& operator=(const Type&) = delete;
private:
constexpr Type(const std::type_info& ti) noexcept : type_info(ti) {}
~Type() = default;
virtual bool can_catch_thrown_pointer(void thrower()) const noexcept = 0;
};
namespace detail {
template<typename T>
struct CType : Type {
static const CType instance;
private:
constexpr CType() noexcept : Type(typeid(T)) {}
~CType() = default;
bool can_catch_thrown_pointer(void thrower()) const noexcept override {
try {
thrower();
} catch (T*) {
return true;
} catch (...) {}
return false;
}
};
template<typename T>
constexpr CType<T> CType<T>::instance{};
}
template<typename T>
constexpr const Type& TypeOf = detail::CType<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::instance;
#include <vector>
#include <iostream>
struct Dog {};
struct Cat {};
struct Hamster {};
struct Labrador : Dog {};
int main() {
std::vector<const Type*> validTypes;
validTypes.push_back(&TypeOf<Dog>);
validTypes.push_back(&TypeOf<Cat>);
validTypes.push_back(&TypeOf<Hamster>);
Labrador* somePet = new Labrador();
for (const Type* t : validTypes) {
if (IsOrInheritsFrom(*somePet, *t)) {
std::cout << "Inherits from " << t->type_info.name() << '\n';
} else {
std::cout << "Does not inherit from " << t->type_info.name() << '\n';
}
}
}
Which throws (Labrador*) nullptr
and each type in the vector sees if it can catch it. And it can be caught as a Dog*
.
You can do something similar with dynamic_cast
. The only way I can think of doing it is having a common base class between all types you want to be able to check, then each runtime type can try to dynamic_cast
: https://godbolt.org/z/s9oearhGP
#include <type_traits>
#include <typeinfo>
#include <memory>
struct RuntimeTypeCheckable {
protected:
RuntimeTypeCheckable() = default;
RuntimeTypeCheckable(const RuntimeTypeCheckable&) = default;
RuntimeTypeCheckable& operator=(const RuntimeTypeCheckable&) = default;
virtual ~RuntimeTypeCheckable() = default;
};
namespace detail {
template<typename T>
struct CType;
}
struct Type {
const std::type_info& type_info;
template<typename T>
friend struct detail::CType;
template<typename T>
friend bool IsOrInheritsFrom(const T& object, const Type& type) noexcept {
return type.can_dynamic_cast_pointer(std::addressof(object));
}
Type(const Type&) = delete;
Type& operator=(const Type&) = delete;
private:
constexpr Type(const std::type_info& ti) noexcept : type_info(ti) {}
~Type() = default;
virtual bool can_dynamic_cast_pointer(const RuntimeTypeCheckable* ptr) const noexcept = 0;
};
namespace detail {
template<typename T>
struct CType : Type {
static const CType instance;
private:
constexpr CType() noexcept : Type(typeid(T)) {}
~CType() = default;
bool can_dynamic_cast_pointer(const RuntimeTypeCheckable* ptr) const noexcept override {
return dynamic_cast<const T*>(ptr) != nullptr;
}
};
template<typename T>
constexpr CType<T> CType<T>::instance{};
}
template<typename T>
constexpr const Type& TypeOf = detail::CType<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::instance;
#include <vector>
#include <iostream>
struct Dog : virtual RuntimeTypeCheckable {};
struct Cat : virtual RuntimeTypeCheckable {};
struct Hamster : virtual RuntimeTypeCheckable {};
struct Labrador : Dog {};
int main() {
std::vector<const Type*> validTypes;
validTypes.push_back(&TypeOf<Dog>);
validTypes.push_back(&TypeOf<Cat>);
validTypes.push_back(&TypeOf<Hamster>);
Labrador* somePet = new Labrador();
for (const Type* t : validTypes) {
if (IsOrInheritsFrom(*somePet, *t)) {
std::cout << "Inherits from " << t->type_info.name() << '\n';
} else {
std::cout << "Does not inherit from " << t->type_info.name() << '\n';
}
}
}
Though this requires the types to have RuntimeTypeCheckable
as a base class.
As for virtual functions, you would need something like this: https://godbolt.org/z/jq6b8E7bx
#include <typeinfo>
struct RuntimeTypeCheckable {
public:
virtual bool is_or_inherits_from(const std::type_info& type_info) const noexcept {
return type_info == typeid(RuntimeTypeCheckable);
}
friend bool IsOrInheritsFrom(const RuntimeTypeCheckable& object, const std::type_info& type) noexcept {
return object.is_or_inherits_from(type);
}
protected:
RuntimeTypeCheckable() = default;
RuntimeTypeCheckable(const RuntimeTypeCheckable&) = default;
RuntimeTypeCheckable& operator=(const RuntimeTypeCheckable&) = default;
virtual ~RuntimeTypeCheckable() = default;
};
#include <typeindex>
#include <vector>
#include <iostream>
struct Dog : virtual RuntimeTypeCheckable {
bool is_or_inherits_from(const std::type_info& type_info) const noexcept override {
return type_info == typeid(Dog) || RuntimeTypeCheckable::is_or_inherits_from(type_info);
}
};
struct Cat : virtual RuntimeTypeCheckable {
bool is_or_inherits_from(const std::type_info& type_info) const noexcept override {
return type_info == typeid(Dog) || RuntimeTypeCheckable::is_or_inherits_from(type_info);
}
};
struct Hamster : virtual RuntimeTypeCheckable {
bool is_or_inherits_from(const std::type_info& type_info) const noexcept override {
return type_info == typeid(Dog) || RuntimeTypeCheckable::is_or_inherits_from(type_info);
}
};
struct Labrador : Dog {
bool is_or_inherits_from(const std::type_info& type_info) const noexcept override {
return type_info == typeid(Labrador) || Dog::is_or_inherits_from(type_info);
}
};
int main() {
std::vector<const std::type_info*> validTypes;
validTypes.push_back(&typeid(Dog));
validTypes.push_back(&typeid(Cat));
validTypes.push_back(&typeid(Hamster));
Labrador* somePet = new Labrador();
for (const std::type_info* t : validTypes) {
if (IsOrInheritsFrom(*somePet, *t)) {
std::cout << "Inherits from " << t->name() << '\n';
} else {
std::cout << "Does not inherit from " << t->name() << '\n';
}
}
}
Which requires the most modifications to the types being used, but is the most readable. The boilerplate can be reduced with CRTP.