jeudi 2 juillet 2015

Mapping runtime types to a parallel class hierarchy … with templates

I have two class hierarchies which have a 1:1 relationship: some normal classes A, B which have a common Root interface, and a WrapperRoot<T> interface with two concrete instantiations WrapperA<T> and WrapperB<T>. I am now looking to implement a function auto wrap<T>(Root& elem) -> unique_ptr<WrapperRoot<T>> that maps each normal class to its wrapper class.

UML class diagram of the described hierarchy

The exact type of the wrappers is important as they will have virtual methods, and the exact type of the Root objects is not statically known.

Attempted solutions

My first idea was to declare a template virtual method in Root:

class Root {
  ...
public:
  template<typename T>
  virtual auto wrap() -> unique_ptr<WrapperRoot<T>> = 0;
}

which could then be implemented in the child classes as

class A : public Root {
  ...
  template<typename T>
  virtual auto wrap() -> unique_ptr<WrapperRoot<T>> override {
    return make_unique<WrapperA<T>>();
  }
}

As I was to find out, C++ does not allow templates for virtual methods. I did some further research and found the technique of type erasure, which allows us to break through this virtual vs. template dichtomy. Perhaps it might be possible to have each class select their wrapper type by passing in a visitor-like object that has erased the template parameter <T>? However, I am still fairly new to C++, and all my attempts to implement this have only moved the problem into another level, but not solved them.

This is especially frustrating since other languages which I am familiar with have no problem expressing this structure. E.g. in Java it is no problem to define a virtual method <T> WrapperRoot<T> wrap() { return new WrapperA<T>(); }, but that is because Java implements templates via reinterpreting casts. Java's implementation would be phrased in C++ as:

template<typename T>
WrapperRoot<T>* wrap() { return reinterpret_cast<WrapperRoot<T>*>(wrapper_impl()); }
virtual void* wrapper_impl() { return new WrapperA<void*>() }

However, I would like to work with the C++ type system rather than violating it by casting void pointers around.

Test Case

To phrase my problem unambiguously, I have created the below test case. Once wrap is implemented correctly, it should output this:

WrapperA
WrapperB

The main method should not be modified, but arbitrary methods, helper types, and an implementation for the wrap function may be added.

#include <iostream>
#include <memory>
using namespace std;

// the Root hierarchy

class Root {
public:
  virtual ~Root() {}
};

class A : public Root {};

class B : public Root {};


// the Wrapper hierarchy

template<typename T>
class WrapperRoot {
public:
  virtual ~WrapperRoot() {}
  virtual T name() = 0;
};

template<typename T>
class WrapperA : public WrapperRoot<T> {
public:
  virtual T name() { return T("WrapperA\n"); }
};

template<typename T>
class WrapperB : public WrapperRoot<T> {
public:
  virtual T name() { return T("WrapperB\n"); }
};


// the "wrap" function I want to implement

template<typename T>
auto wrap(Root& ) -> unique_ptr<WrapperRoot<T>>;

// util
template<typename T, typename... Args>
auto make_unique(Args... args) -> unique_ptr<T> {
  return unique_ptr<T>(new T(forward<Args>(args)...));
}

int main() {
  unique_ptr<Root> a = make_unique<A>();
  unique_ptr<Root> b = make_unique<B>();
  cout << wrap<string>(*a)->name()
       << wrap<string>(*b)->name();
}

How can I make this work? Or do I need to resort to type-violating hacks?

Aucun commentaire:

Enregistrer un commentaire