Search code examples
c++introspection

Efficient way to define an introspective C++ class hierararchy description ?


I have a C++ class hierarchy defined by inheritance and I store a description of this hierarchy in it that I can later use for introspection. I would like to know if there is a more efficient or cleaner way to define this than the way I currently do it. Here is a stripped down version of my code

// in header file (hpp)
struct Type
{
    Type( const string& n, const Type* p = nullptr ) : name(n), parent(p) {}
    const string name;
    const Type* parent;
};

class Base
{
public:
    static const Type m_type;
    virtual const Type& type() const { return m_type; } 
};

class Derived : public Base
{
public:
    static const Type m_type;
    const Type& type() const { return m_type; }
};

// in implementation file (cpp)
const Type Base::m_type( "Base" );
const Type Derived::m_type( "Derived", &Base::m_type );

Solution

  • Not necessarily more efficient but think whether you actually want to require a common base class. An alternative approach uses a global type information registry. Then querying a type’s type information is done via TypeInfo::get(my_variable) or TypeInfo::get(typeid(my_type)).

    This has the advantage that it also works with existing types, which just need to be added to this type info registry.

    Internally, the registry would use a map from std::type_info to Type or similar. The following is a proof of concept. Unfortunately the code doesn’t compile on either clang or GCC. Based on the error messages, I’m suspecting a bug but I could also be wrong …

    struct Type {
        std::string name;
        std::vector<Type*> parents;
        // TODO Extend by fully-qualified name (namespace) etc.
    
        template <typename... T>
        Type(std::string&& name, T*... parents)
            : name(name), parents{parents...} { }
    };
    
    struct TypeInfo {
        template <typename T>
        static Type const& get(T const&) { return get(typeid(T)); }
    
        template <typename T>
        static Type const& get() { return get(typeid(T)); }
    
        static Type const& get(std::type_info const& info) {
            auto i = types.find(info);
            if (i == types.end())
                throw unknown_type_error(info.name());
    
            return i->second;
        }
    
        template <typename T>
        static void register_type(Type&& type) {
            types.insert(std::make_pair(typeid(T), type));
        }
    
        typedef std::unordered_map<std::type_info, Type> type_dir_t;
        static type_dir_t types;
    };
    

    Full code available as gist on github.

    Having a common base class for logically unrelated classes is generally frowned upon in C++, although it could be argued that this is similar to CRTP / mixins, in which common base classes are encouraged. So I’d say that there isn’t necessarily anything wrong with the approach if you don’t care for existing types.