Is there a way to point to a constructor from a std::map? I'd like to do the following, with the code I desire to use in #if 0
, but I can't seem to get this to work:
#include <map>
#include <functional>
using namespace std;
class Base { };
class A : public Base { };
class B : public Base { };
enum class Type { A, B, };
#if 0
using type_map_t = std::map<Type, std::function<Base*()>>;
type_map_t type_map = {
{Type::A, &A::A},
{Type::B, &B::B},
};
#endif
Base*
getBase(Type t)
{
#if 0
auto constructor = type_map[t];
return constructor();
#else
switch(t)
{
case Type::A:
return new A();
case Type::B:
return new B();
}
#endif
}
int
main(int argc, char *argv[])
{
Base *base = getBase(Type::A);
return 0;
}
Rather than have a switch statement in getBase, I'd rather have the map indicate what constructor gets called for each type.
std::function comes to mind for how to do this, but it doesn't seem possible to get the address of a constructor in C++. Is there an elegant way to accomplish what I want to do here?
A couple of points:
A constructor is a bit of code that takes raw memory and turns it into an object. This means you have to have exactly the right amount of memory available.
There is no mechanism for you to access a constructor like a normal function because of that: the constructor's this
pointer is already known when construction starts, and there's no way to pass one in.
The way around these limitations is to use operator new
. The usual operator new
will allocate the memory needed for the object and apply the constructor code if allocation was successful. (Alternatively, there is a "placement new
" that allows the programmer to provide a pointer to "enough memory" for the object. It's used for "emplaced" construction, where you've allocated a suitable buffer beforehand. Generally that stuff lives in container libraries only.)
So what you put in your map is going to have to use new
in the functions. The usual new
will do since you don't have any mechanism for passing in raw memory.
Using lambdas, your map could look like this:
type_map_t type_map = {
{Type::A, []() -> Base* { return new A; } },
{Type::B, []() -> Base* { return new B; } },
};
The construct []() -> Base*
says that the following code block is treated as a function body taking no arguments (nothing in the ()
-list), and nothing from surrounding scope (nothing in the []
-list), and returning a Base*
. It's usable as an initialization value for the std::function<Base*()>
holder object.
You can call your map entry directly too:
Base* base = type_map[Type::A]();