Search code examples
c++c++11decltype

Is it possible to store the type of an object in a map for casting purposes


Sorry for the confusing title as I am not sure how to word it. What I am trying to do is basically cast to a type that will dynamically retrieved from a map. (The map will not have an instance but will have a type if that is possible)

Allow me to explain with an example. Suppose I have a base class called baseFoo

class baseFoo
{
}

and then I have two different classes that inherit from baseFoo called derFooA and derFooB

class derfooA : public baseFoo
{
}

and similarly for derfooB

class derfooB : public baseFoo
{
}

Now I wanted to know if I can only store a type in a map (not an instance of type - simply a variable type) something like this

//STATEMENT A:
std::map<std::string , baseFoo> myMap= {{"derfooA",derfooA}, {"derfooB", derfooB}}; 

Then I would like to do something like this:

say I have a baseFoo ptr and I would like to DownCast the ptr to a specific type based on a string. So I could do this:

std::string str = "derfooA";
derfooA* dfoo = dynamic_cast<myMap[str]>(baseFoo_ptr)

Now my question is if such a mechanism is possible what would my statement a look like ?


Solution

  • template<class...Ts>
    struct types { using type=types; };
    

    this is a bundle of types.

    template<class T>struct tag_t{constexpr tag_t(){}; using type=T;};
    template<class T>constexpr tag_t<T> tag{};
    

    this is a type tag.

    template<class T, class U>
    T* tag_dynamic_cast( tag_t<T>, U* u ) {
      return dynamic_cast<T*>(u);
    }
    template<class T, class U>
    T& tag_dynamic_cast( tag_t<T>, U& u ) {
      return dynamic_cast<T&>(u);
    }
    

    this lets you dispatch a dynamic cast based on a tag.

    template<class F, class Sig>
    struct invoker;
    template<class F, class R, class...Args>
    struct invoker<F, R(Args...)> {
      R(*)(void*, Args...) operator()() const {
        return [](void* ptr, Args...args)->R {
          return (*static_cast<F*>(ptr))(std::forward<Args>(args)...);
        };
      }
    };
    template<class F, class...Sigs>
    std::tuple<Sigs*...> invokers() {
      return std::make_tuple( invoker<F, Sigs>{}()... );
    }
    

    so at this point we have a tuple of pointers to functions that will invoke a given object F type with a set of signatures Sigs for each signature.

    We next would write a dipatcher which would select the "right" Sigs from Sigs... based on some call to it based on a std::tuple of Sigs* with a void* first argument.

    Now we take a types<...> of the types you want to support to cast-to. We use that to generate a type-erasure class which stores a passed-in lambda in a std::unique_ptr<void, void(*)(void*)>. The signatures we support are tag_t<x> where x varies over the types in types<x...>.

    This is your dynamic dispatcher.

    We are most of the way there. Next we build something that takes a dynamic dispatcher and a void*, and invokes it with tag_t<x>. It erases which tag_t<x> it will invoke the dynamic dispatcher with so the interface doesn't expose it. It is basically a binder to a std function at this point, easy.

    //STATEMENT A:
    std::map<std::string , helper<derFooA, derFooB>> myMap=
      {{"derfooA",tag<derfooA>}, {"derfooB", tag<derfooB>}}; 
    myMap[str]( [&](auto&& tag) {
      auto* dfoo = tag_dynamic_cast(tag, baseFoo_ptr);
    } );
    

    and while the lambda will be instantiated for every derived type, the only one that will be run is the one matching the value stored in the map.

    You'll note you are trapped within the lambda. Yep. You cannot "leak" real type information out either, other than as run-time state.

    Now, odds are your real problem has an easier solution. But this is a roadmap to how to do it. It would take me too much time to write it myself.