I have two C++ abstract classes Abs1
and Abs2
. Then I have:
`A : public Abs1`
`B : public Abs1`
`C : public Abs2`
`D : public Abs2`
Now, I'm trying to create objects from command line arguments and I have to do rewrite the public factory function make_abstract
in the linked question, something like:
std::unique_ptr<Abs1> makeAbs1 (int argc, const char*argv[])
{
if (argc == 1) {
return nullptr;
}
const std::string name = argv[1];
if (name == "A") {
return detail::make_abstract<A, std::tuple<int, std::string, int>>(argc, argv);
} else if (name == "B") {
return detail::make_abstract<B, std::tuple<int, int>>(argc, argv);
}
}
std::unique_ptr<Abs2> makeAbs2 (int argc, const char*argv[])
{
if (argc == 1) {
return nullptr;
}
const std::string name = argv[1];
if (name == "C") {
return detail::make_abstract<C, std::tuple<int>>(argc, argv);
} else if (name == "D") {
return detail::make_abstract<D, std::tuple<int, float>>(argc, argv);
}
}
As you can see this is terribly redundant. How can I do a generic version of this? In this version we can pass as many implemented class as we want, so the if
cascade is not a solution. Notice that we cannot modify any of these classes.
I was thinking that maybe variadic templates could help, but I can't figure out many problems:
template <typename T, typename ...Ts>
std::unique_ptr<T> make (int argc, const char*argv[]){
const std::string name = argv[1];
for(Ti : Ts) //this is obviously wrong
if(typeid(Ti).name == name)
return detail::make_abstract<T, std::tuple</*Here shoudl be different for every Ti*/>>(argc, argv);
}
Ooooh, this was fun :)
[TL;DR: there's a live example at the bottom]
I have implemented two layers of mapping on top of your
detail::make_abstract
function(s). Let's start from the calling code:Here we are calling
makeEverything
withargc
,argv
, and a list ofstd::unique_ptr
s. After the function ends, one of the pointers will hold an object of the correct type.Let's go deeper.
This is your usual recursive variadic function template: take the first pointer, try to construct an object for it. If it failed, throw it away and retry with the next one. You could put some error handling inside the base-case overload at the top: it will be called when no object could be constructed at all.
So now we know which one of
Abs1
,Abs2
or whatever is the desired base class.Let's go deeper.
makeAbs
checks and retrievesargv[1]
. Then it uses it as a key into a map of factory functions, to retrieve the factory corresponding to that name, and then call it and return the resulting object.If no object of that name is known,
std::map::at()
will throwstd::out_of_bounds
. Of course, you can change that error handlingNow let's see how we can populate the factory maps, it is actually quite easy:
You just have to provide a definition of
FactoryMap<Abs>::map
for eachAbs
you wish to use. Since this is an object definition, this should be put inside a .cpp file. Note that, as a bonus, you can add new classes and their mappings without recompiling anything else!Final piece of the puzzle:
detail::make_abstract_erased
. You haven't provided the declaration ofdetail::make_abstract
, but it looks like it returnsstd::unique_ptr<T>
, withT
being its first template argument.Given that C++ does not allow converting between function pointers that differ in return types (and for good reasons), we need that additional layer just to wrap
detail::make_abstract
and perform the conversion:And that's it!
See it live on Coliru