ToniBig
ToniBig

Reputation: 836

Resolving types in a hierarchy of classes using std::type_index

I was looking for an alternative to the Visitor pattern to map behaviour for types in a hierarchy of classes and maybe allow for multiple dispatch. Any time you add a new type to a hierarchy of classes, you have to update all the supporting visitors. So I came across std::type_index, which according to cppreference.com might do the trick. I then thought, now I have to implement a get_type_index for every subclass, which would be related to the accept method of the visitor pattern. This would then only be half of the work that has to be done for the Visitor pattern. But then I saw that apparently you only have to implement a (non pure) virtual function in the base class to make the whole thing work. Below is my code and I have the following question(s):

#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <typeindex>

struct Base
{
  std::type_index get_type_index( ) const
  {
    return std::type_index( typeid(*this) );
  }
};

struct BaseVirtual
{
  virtual std::type_index get_type_index( ) const
  {
    return std::type_index( typeid(*this) );
  }
};

template<typename BaseType>
struct A : public BaseType
{
};

template<typename BaseType>
struct B : public BaseType
{
};

template<typename BaseType>
struct C : public BaseType
{
};

int main( int argc,
          char **argv )
{
  std::map<std::type_index, std::string> typeToStringMap = { { std::type_index( typeid(Base) ), "Base" }, { std::type_index( typeid(A<Base> ) ), "ABase" }, { std::type_index( typeid(B<Base> ) ), "BBase" }, { std::type_index( typeid(C<Base> ) ), "CBase" }, { std::type_index( typeid(BaseVirtual) ),
      "BaseVirtual" }, { std::type_index( typeid(A<BaseVirtual> ) ), "ABaseVirtual" }, { std::type_index( typeid(B<BaseVirtual> ) ), "BBaseVirtual" }, { std::type_index( typeid(C<BaseVirtual> ) ), "CBaseVirtual" } };

  A<Base> a;
  B<Base> b;
  C<Base> c;

  A<BaseVirtual> av;
  B<BaseVirtual> bv;
  C<BaseVirtual> cv;

  auto asp = std::make_shared<A<Base>>( );
  auto bsp = std::make_shared<B<Base>>( );
  auto csp = std::make_shared<C<Base>>( );

  auto avsp = std::make_shared<A<BaseVirtual>>( );
  auto bvsp = std::make_shared<B<BaseVirtual>>( );
  auto cvsp = std::make_shared<C<BaseVirtual>>( );

  A<Base>* ap = new A<Base>;
  B<Base>* bp = new B<Base>;
  C<Base>* cp = new C<Base>;

  A<BaseVirtual>* avp = new A<BaseVirtual>;
  B<BaseVirtual>* bvp = new B<BaseVirtual>;
  C<BaseVirtual>* cvp = new C<BaseVirtual>;

  std::cout << typeToStringMap[a.get_type_index( )] << std::endl;
  std::cout << typeToStringMap[b.get_type_index( )] << std::endl;
  std::cout << typeToStringMap[c.get_type_index( )] << std::endl;
  std::cout << typeToStringMap[av.get_type_index( )] << std::endl;
  std::cout << typeToStringMap[bv.get_type_index( )] << std::endl;
  std::cout << typeToStringMap[cv.get_type_index( )] << std::endl;

  std::cout << typeToStringMap[asp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[bsp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[csp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[avsp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[bvsp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[cvsp->get_type_index( )] << std::endl;

  std::cout << typeToStringMap[ap->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[bp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[cp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[avp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[bvp->get_type_index( )] << std::endl;
  std::cout << typeToStringMap[cvp->get_type_index( )] << std::endl;
}

Output:

Base
Base
Base
ABaseVirtual
BBaseVirtual
CBaseVirtual
Base
Base
Base
ABaseVirtual
BBaseVirtual
CBaseVirtual
Base
Base
Base
ABaseVirtual
BBaseVirtual
CBaseVirtual

Upvotes: 0

Views: 1013

Answers (1)

Igor Tandetnik
Igor Tandetnik

Reputation: 52611

Why are types mapped correctly for classes inheriting from BaseVirtual

Because the C++ standard says they should:

[expr.typeid]/2 When typeid is applied to a glvalue expression whose type is a polymorphic class type (10.3), the result refers to a std::type_info object representing the type of the most derived object (1.8) (that is, the dynamic type) to which the glvalue refers.


How does the compiler do it and how expensive is it?

Irrelevant implementation details. One way is to add a pointer to type_info, or something substantially similar, to the vtable.


Is it portable?

Yes it is. Again, the behavior you observe is mandated by the C++ standard.

Upvotes: 1

Related Questions