How to get a type from type_info for template parameters
Asked Answered
U

3

5

I have a tree where each node basically looks like this:

struct node
{
    std::unordered_set<object*> objects;
    std::map<std::type_index,node> children;
};

When i iterate over the tree to add a new type, i want to do a check :

std::is_base_of<base,derived>

However, the only information i have for the derived type is a type_index/type_info*.

Is there anyway i can transform the type_info* to a template argument?

If not, what are my other options? I guess could call decltype(*objects.begin()), but that would require each set in every node to never be empty.

Ushaushant answered 5/4, 2013 at 11:24 Comment(1)
Templates are compile-time evaluated and you have a type_info representing the runtime-type of your objects. How should this ever be possible. You already know their static type, it's object* (and that's also what decltype(*objects.begin()) will return), but that doesn't help you in any way. So templates are entirely out of the question, since the solve a completely different problem and cannot be used for RTTI.Caaba
K
4

If I understood you correctly, you want a type inheritance_checker such that each instation of it is associated to a type but inheritance_checker itself is not. Something similar to type_ifo but that can check inheritance relationships at runtime. For example, you want the following to work:

class A {};
class B : public A {};

// Note that a and b have the same type but they are "related" to distinct types.
inheritance_checker a = inheritance_checker::create<A>();
inheritance_checker b = inheritance_checker::create<B>();

assert(   a.is_base_of  (b) );
assert(   a.derives_from(a) ); // derives from or is equal to
assert( ! b.is_base_of  (a) );
assert(   b.derives_from(b) ); // derives from or is equal to

If this is the case, I can suggest you something that, unfortunately, can be quite slow! It depends on exceptions.

The basic idea is that if you throw a B* then a catch (A*) is a match. Hence, we give to inheritance_checker the ability to throw and catch pointers to the type given at construction time. But inheritance_checker is not a template and therefore this capacity must be provided in a type-erased way. This can be done by storing pointers to functions that have a fixed signature that doesn't depend on the type passed at construction but are capable of throwing/catching pointers to the given type. A possible implementation of inheritance_checker is given below.

#include <cassert>

class inheritance_checker {

    typedef void (*thrower_t)();
    typedef bool (*catcher_t)(thrower_t);

public:

    template <typename T>
    static inheritance_checker create() {
        return inheritance_checker(concrete_thrower<T>, concrete_catcher<T>);
    } 

    bool is_derived_from(const inheritance_checker& other) const {
        return other.catcher_(thrower_);
    }

    bool is_base_of(const inheritance_checker& other) const {
        return catcher_(other.thrower_);
    }

private:

    template <typename T>
    static void concrete_thrower() {
        throw static_cast<T*>(nullptr);
    }

    template <typename T>
    static bool concrete_catcher(thrower_t thrower) {
        try         { thrower();   }
        catch (T*)  { return true; }
        catch (...) {              }
        return false;
    }

    inheritance_checker(thrower_t thrower, catcher_t catcher) :
        thrower_(thrower), catcher_(catcher) {
    }

    thrower_t thrower_;
    catcher_t catcher_;
};

class A {};
class B : public A {};
class C : public B {};
class D {};

int main() {

    auto a = inheritance_checker::create<A>();
    auto b = inheritance_checker::create<B>();
    auto c = inheritance_checker::create<C>();
    auto d = inheritance_checker::create<D>();

    assert( a.is_base_of(a));
    assert( a.is_base_of(b));
    assert( a.is_base_of(c));
    assert(!a.is_base_of(d));

    assert( a.is_derived_from(a));
    assert(!a.is_derived_from(b));
    assert(!a.is_derived_from(c));
    assert(!a.is_derived_from(d));

    assert(!b.is_base_of(a));
    assert( b.is_base_of(b));
    assert( b.is_base_of(c));
    assert(!b.is_base_of(d));

    assert( b.is_derived_from(a));
    assert( b.is_derived_from(b));
    assert(!b.is_derived_from(c));
    assert(!b.is_derived_from(d));

    assert(!c.is_base_of(a));
    assert(!c.is_base_of(b));
    assert( c.is_base_of(c));
    assert(!c.is_base_of(d));

    assert( c.is_derived_from(a));
    assert( c.is_derived_from(b));
    assert( c.is_derived_from(c));
    assert(!c.is_derived_from(d));

    assert(!d.is_base_of(a));
    assert(!d.is_base_of(b));
    assert(!d.is_base_of(c));
    assert( d.is_base_of(d));

    assert(!d.is_derived_from(a));
    assert(!d.is_derived_from(b));
    assert(!d.is_derived_from(c));
    assert( d.is_derived_from(d));
}

If you wish, you might add a type_info* member to inheritance_checker to get the extra functionality that type_info provides.

Notice the symmetry between is_base_of and derives_from. Actually, you can remove one of them.

I suggest you to read this article.

Kneecap answered 5/4, 2013 at 14:13 Comment(2)
This is really awesome! I did not follow this exactly but it really gave me ideas! What really is important here is the function pointers. I achieved what i needed by giving my nodes a lambda on creation (like your inheritance_checker::create<>). The lambda is created via template and calls dynamic_cast<T>. I can then call the lambda WITHOUT templates. THANK YOU. I knew it would be possible to mix static and dynamic typing!Ushaushant
@Ushaushant Jensen: Indeed, if all your types under consideration derive from the same polymorphic base class, then it's more efficient to use dynamic_cast than throw/catch as I did. The advantage of the throw/catch trick is that it's more general but the price to pay is that it's slower.Kneecap
B
7

Is there anyway i can transform the type_info* to a template argument?

No, there is no way. Templates are a compile-time thing, RTTI is run-time. No connection between the two.

I guess could call decltype(*objects.begin()), but that would require each set in every node to never be empty.

It would not require that. decltype doesn't evaluate its argument - it doesn't need to. It only needs to check the types. You can happily do decltype(*a_null_pointer) without invoking UB, since the expression is never evaluated - this is a so-called unevaluated context. sizeof belongs in the same category.

Note that this doesn't buy you much, though - you'll just get object*& back. You generally can't get a type from run-time information without preparing the mapping first.

Bane answered 5/4, 2013 at 11:44 Comment(7)
Does decltype(base*) give me the "real" derrived type? Because my sets of object* does hold more defined types than object*.Ushaushant
@David: Oh, no, obviously not, since it's just a compile-time thing aswell. :) You can't get a type from runtime information without preparing a mapping before-hand.Bane
You might explicitly add to your second paragraph, that decltype(*objects.begin()) won't buy anything at all, just returning object*, even if your first paragraph already states this in general.Caaba
As ecatmur said i obviously need to do the check trough dynamic_cast. But dynamic cast still takes a type and not a type_info. So i am back to square one.Ushaushant
@DavidJensen It doesn't just take a type, but also an actual object (or rather a pointer/reference to one). But do you say you don't even have an object, but only its type_info? Interresting, but you won't get around using the object the type_info was generated from, if you can still access it. In the end now might be a good time to explain the actual problem you're trying to solve more clearly in your question.Caaba
The type_info was generated by typeid() which, in opposite to dynamic_cast, only requires a type but not a object. I solved the problem however by giving each node a void* which points to a testtype i can do dynamic_cast on. Its quite some memory overhead but will do for now.Ushaushant
@DavidJensen dynamic_casting a void*, does that even have defined semantics? So you created the type_info from an actual type known at compile time (since it's a type) and don't have any object it belongs to (you know typeid can work on objects, which is its whole purpose in the end)? Then you seem to know the type at compile time anyway and there isn't a need for runtime-polymorphism. Either seem to be mixing static and dynamic typing in either an interresting or a weird way, don't which yet ;)Caaba
B
4

is_base_of and decltype are purely compile time constructs; they won't reflect the dynamic type of your objects.

type_info doesn't provide facilities to check subclass relationships; the only way to perform that check at runtime is to use dynamic_cast. You'd need to store a unique_ptr<base> and see whether it can dynamic_cast to the derived type.

Bor answered 5/4, 2013 at 11:51 Comment(3)
Unfortunately i guess your right. Damn this will make my tree structure slow.Ushaushant
But still, dynamic_cast requires a real type and not a type_info*. I am back to square one.Ushaushant
@Ushaushant The idea is to get rid of the type_info completely.Mentholated
K
4

If I understood you correctly, you want a type inheritance_checker such that each instation of it is associated to a type but inheritance_checker itself is not. Something similar to type_ifo but that can check inheritance relationships at runtime. For example, you want the following to work:

class A {};
class B : public A {};

// Note that a and b have the same type but they are "related" to distinct types.
inheritance_checker a = inheritance_checker::create<A>();
inheritance_checker b = inheritance_checker::create<B>();

assert(   a.is_base_of  (b) );
assert(   a.derives_from(a) ); // derives from or is equal to
assert( ! b.is_base_of  (a) );
assert(   b.derives_from(b) ); // derives from or is equal to

If this is the case, I can suggest you something that, unfortunately, can be quite slow! It depends on exceptions.

The basic idea is that if you throw a B* then a catch (A*) is a match. Hence, we give to inheritance_checker the ability to throw and catch pointers to the type given at construction time. But inheritance_checker is not a template and therefore this capacity must be provided in a type-erased way. This can be done by storing pointers to functions that have a fixed signature that doesn't depend on the type passed at construction but are capable of throwing/catching pointers to the given type. A possible implementation of inheritance_checker is given below.

#include <cassert>

class inheritance_checker {

    typedef void (*thrower_t)();
    typedef bool (*catcher_t)(thrower_t);

public:

    template <typename T>
    static inheritance_checker create() {
        return inheritance_checker(concrete_thrower<T>, concrete_catcher<T>);
    } 

    bool is_derived_from(const inheritance_checker& other) const {
        return other.catcher_(thrower_);
    }

    bool is_base_of(const inheritance_checker& other) const {
        return catcher_(other.thrower_);
    }

private:

    template <typename T>
    static void concrete_thrower() {
        throw static_cast<T*>(nullptr);
    }

    template <typename T>
    static bool concrete_catcher(thrower_t thrower) {
        try         { thrower();   }
        catch (T*)  { return true; }
        catch (...) {              }
        return false;
    }

    inheritance_checker(thrower_t thrower, catcher_t catcher) :
        thrower_(thrower), catcher_(catcher) {
    }

    thrower_t thrower_;
    catcher_t catcher_;
};

class A {};
class B : public A {};
class C : public B {};
class D {};

int main() {

    auto a = inheritance_checker::create<A>();
    auto b = inheritance_checker::create<B>();
    auto c = inheritance_checker::create<C>();
    auto d = inheritance_checker::create<D>();

    assert( a.is_base_of(a));
    assert( a.is_base_of(b));
    assert( a.is_base_of(c));
    assert(!a.is_base_of(d));

    assert( a.is_derived_from(a));
    assert(!a.is_derived_from(b));
    assert(!a.is_derived_from(c));
    assert(!a.is_derived_from(d));

    assert(!b.is_base_of(a));
    assert( b.is_base_of(b));
    assert( b.is_base_of(c));
    assert(!b.is_base_of(d));

    assert( b.is_derived_from(a));
    assert( b.is_derived_from(b));
    assert(!b.is_derived_from(c));
    assert(!b.is_derived_from(d));

    assert(!c.is_base_of(a));
    assert(!c.is_base_of(b));
    assert( c.is_base_of(c));
    assert(!c.is_base_of(d));

    assert( c.is_derived_from(a));
    assert( c.is_derived_from(b));
    assert( c.is_derived_from(c));
    assert(!c.is_derived_from(d));

    assert(!d.is_base_of(a));
    assert(!d.is_base_of(b));
    assert(!d.is_base_of(c));
    assert( d.is_base_of(d));

    assert(!d.is_derived_from(a));
    assert(!d.is_derived_from(b));
    assert(!d.is_derived_from(c));
    assert( d.is_derived_from(d));
}

If you wish, you might add a type_info* member to inheritance_checker to get the extra functionality that type_info provides.

Notice the symmetry between is_base_of and derives_from. Actually, you can remove one of them.

I suggest you to read this article.

Kneecap answered 5/4, 2013 at 14:13 Comment(2)
This is really awesome! I did not follow this exactly but it really gave me ideas! What really is important here is the function pointers. I achieved what i needed by giving my nodes a lambda on creation (like your inheritance_checker::create<>). The lambda is created via template and calls dynamic_cast<T>. I can then call the lambda WITHOUT templates. THANK YOU. I knew it would be possible to mix static and dynamic typing!Ushaushant
@Ushaushant Jensen: Indeed, if all your types under consideration derive from the same polymorphic base class, then it's more efficient to use dynamic_cast than throw/catch as I did. The advantage of the throw/catch trick is that it's more general but the price to pay is that it's slower.Kneecap

© 2022 - 2024 — McMap. All rights reserved.