Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatic compile-time factory registration of class templates in C++

I'm looking for an abstract factory for class templates, where the classes register themselves automatically at static initialization time. For regular (non-templated) classes, the solution is straightforward enough using static members. Here's an example of a (rather simplistic) solution that works just fine:

#include <cassert>
#include <iostream>

class Base {
 public:
  virtual size_t id() const = 0;
  virtual const char* name() const = 0;
  virtual ~Base() {}
};

typedef Base* (*CreateFunc)(void);

class SimpleFactory {
 private:
  static const size_t NELEM = 2;
  static size_t id_;
  static CreateFunc creators_[NELEM];

 public:
  static size_t registerFunc(CreateFunc creator) {
    assert(id_ < NELEM);
    assert(creator);
    creators_[id_] = creator;
    return id_++;
  }

  static Base* create(size_t id) { assert(id < NELEM); return (creators_[id])(); }
};

size_t SimpleFactory::id_ = 0;
CreateFunc SimpleFactory::creators_[NELEM];


class D1 : public Base {
 private:
  static Base* create() { return new D1; }
  static const size_t id_;

 public:
  size_t id() const { return id_; }
  const char* name() const { return "D1"; }
};

const size_t D1::id_ = SimpleFactory::registerFunc(&create);

class D2 : public Base {
 private:
  static Base* create() { return new D2; }
  static const size_t id_;

 public:
  size_t id() const { return id_; }
  const char* name() const { return "D2"; }
};

const size_t D2::id_ = SimpleFactory::registerFunc(&create);

int main() {
  Base* b1 = SimpleFactory::create(0);
  Base* b2 = SimpleFactory::create(1);
  std::cout << "b1 name: " << b1->name() << "\tid: " << b1->id() << "\n";
  std::cout << "b2 name: " << b2->name() << "\tid: " << b2->id() << "\n";
  delete b1;
  delete b2;
  return 0;
}

The question I have is how to make it work when the stuff I want to register/create is more like:

template <typename T> class Base...
template <typename T> class D1 : public Base<T> ...

The best idea I can think of is to template the factory as well, something like:

 template <typename T>
 class SimpleFactory {
 private:
  static const size_t NELEM = 2;
  static size_t id_;
  typedef Base<T>* Creator;
  static Creator creators_[NELEM];
...(the rest remains largely the same)

But I'm wondering if there's a better way, or if someone has implemented such a pattern before.

EDIT: revisiting this problem a few years later (and with variadic templates), I can get much closer to what I want by simply "registering" functions, or rather classes, as template parameters to the factory. It would look something like this:

#include <cassert>

struct Base {};

struct A : public Base {
  A() { std::cout << "A" << std::endl; }
};

struct B : public Base {
  B() { std::cout << "B" << std::endl; }
};

struct C : public Base {
  C() { std::cout << "C" << std::endl; }
};

struct D : public Base {
  D() { std::cout << "D" << std::endl; }
};


namespace {
  template <class Head>
  std::unique_ptr<Base>
  createAux(unsigned id)
  {
    assert(id == 0);
    return std::make_unique<Head>();
  }

  template <class Head, class Second, class... Tail>
  std::unique_ptr<Base>
  createAux(unsigned id)
  {
    if (id == 0) {
      return std::make_unique<Head>();
    } else {
      return createAux<Second, Tail...>(id - 1);
    }
  }
}

template <class... Types>
class LetterFactory {
 public:
  std::unique_ptr<Base>
  create(unsigned id) const
  {
    static_assert(sizeof...(Types) > 0, "Need at least one type for factory");
    assert(id < sizeof...(Types));
    return createAux<Types...>(id);
  }
};

int main() {
  LetterFactory<A, B, C, D> fac;
  fac.create(3);
  return 0;
}

Now, this is just a simplistic prototype, so never mind create()'s linear complexity. The main deficiency of this design, however, is that it doesn't allow for any constructor parameters. Ideally, I'd be able to register not only the classes the factory needs to create, but also the types each class takes in its constructor, and let create() take them variadically. Has anyone ever done something like this before?

like image 821
Eitan Avatar asked Oct 06 '11 19:10

Eitan


1 Answers

I posted an answer to a similar issue over at GameDev, but the solution is not compile time. You can check it out here:
> https://gamedev.stackexchange.com/questions/17746/entity-component-systems-in-c-how-do-i-discover-types-and-construct-components/17759#17759

I don't think there's even a way to make this compile time. Your "id" inside of the base class is really just a simplified form of RTTI, which is by definition run-time. Maybe if you made the id a template argument... but that would make some other things a lot more complicated.

like image 53
Paul Manta Avatar answered Nov 12 '22 19:11

Paul Manta