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.
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