5

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?

firebush
  • 5,180
  • 4
  • 34
  • 45

3 Answers3

5

You cannot take the address of a constructor according to C++ standard (§ 12.1.12 of C++98/03 and § 12.1.10 of C++11):

Constructors - The address of a constructor shall not be taken.

For this problem the typical solution is to create specific factories/methods that creates object.

CyberGuy
  • 2,783
  • 1
  • 21
  • 31
5

A couple of points:

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

  2. 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]();
TonyB
  • 158
  • 1
  • 3
3

While directly getting a pointer to a ctor is not possible, you can create your own ctor objects:

template<class Sig>
struct ctor_t;

template<class T, class...Args>
struct ctor_t<T(Args...)> {
  T* operator()(Args...args)const {
    return new T(std::forward<Args>(args)...);
  }
  T* operator()(void*p, Args...args)const {
    return new(p) T(std::forward<Args>(args)...);
  }
  using simple = T*(*)(Args...args);
  using placement = T*(*)(void*, Args...args);
  operator simple()const {
    return [](Args...args)->T* {
      return ctor_t{}(std::forward<Args>(args)...);
    };
  }
  operator placement()const {
    return [](void*p, Args...args)->T* {
      return ctor_t{}(p, std::forward<Args>(args)...);
    };
  }
};

template<class Sig>
static const ctor_t<Sig> ctor = {};

which lets you create an object that acts like a ctor and can be turned into a function pointer as well.

live example.

The above uses some C++14. Replace ctor with:

template<class Sig>
constexpr ctor_t<Sig> ctor() { return {}; }

and its use from ctor<A()> to ctor<A()>() for C++11 (without variable templates).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I appreciate this instructive answer, but I will accept TonyB's lambda answer for its relative simplicity. – firebush Sep 16 '15 at 02:03