In the CRTP, I want to inject the constructor into the derived class, cleanly - without use of macros and without writing it out. It seems it's impossible, so I've come up with some workarounds.
First, there's an underlying event class (QEvent) that should have a unique integer type tag for every derived class (see rationale). You obtain it by calling a registration function It's easy enough to create a CRTP wrapper that will hide this from you:
template <typename Derived> class EventWrapper : public QEvent {
public:
EventWrapper() : QEvent(staticType()) {}
static QEvent::Type staticType() {
static QEvent::Type type = static_cast<QEvent::Type>(registerEventType());
return type;
}
};
class MyEvent1 : public EventWrapper<MyEvent1> {}; // easy-peasy
class MyEvent2 : public EventWrapper<MyEvent2> {};
Note that MyEvent1::staticType() != MyEvent2::staticType()
: registerEventType()
returns unique types each time it's called.
Now I want the event class to carry some data:
template <typename Derived> class StringEvent : public EventWrapper<D> {
std::string m_str;
public:
explicit StringEvent(const std::string & str) : m_str(str) {}
std::string value() const { return m_str; }
};
But here we run into a problem: we need to manually define the constructor in each of the derived classes. The whole point here is that creation of such classes should be easy, as there may be many different string-carrying event types. But it's anything but easy:
class MyEvent3 : public StringEvent<MyEvent3> {
public: MyEvent3(std::string s) : StringEvent(s) {}
};
This obviously gets old real quick, even with C++11 constructor forwarding:
class MyEvent3 : public StringEvent<MyEvent3> { using StringEvent::StringEvent; };
What we'd want is a way of injecting this constructor into the derived class, or avoiding doing so while still providing for ease of use. Sure you can hide it in a preprocessor macro, but I hate those macros, they are a maintenance pain as they introduce new names for very simple concepts.
We can of course use a dummy type. Note that there's no need for a definition of the dummy type. It's only a name to be used as the type argument.
// Pre-C++11
class DummyEvent3;
typedef StringEvent<DummyEvent3> MyEvent3;
// C++11
class DummyEvent3;
using MyEvent3 = StringEvent<DummyEvent3>;
Another solution would be to use an int
template argument and use an enum value, but this brings back the uniqueness issue that got solved by using the registerEventType()
in the first place. It'd be no fun to guarantee that a large program is correct. And you'd still need to spell out the enum.
So, I've come up with a metaprogram class that I'll call a metafactory, that can produce the ready-to-use StringEvent
classes for us, while keeping it all to one type definition:
// the metafactory for string events
template <typename Derived> class StringEventMF {
public:
class Event : public EventWrapper<Derived> {
std::string m_str;
public:
explicit Event(const std::string & val) : m_str(val) {}
std::string value() const { return m_str; }
};
};
or simply
template <typename Derived> class StringEventMF {
public:
typedef StringEvent<Derived> Event;
};
This is used like:
class Update : public StringEventMF<Update> {};
class Clear : public StringEventMF<Clear> {};
void test() {
Update::Event * ev = new Update::Event("foo");
...
}
The classes you use are Update::Event
, Clear::Event
. The Update
and Clear
are metafactories: they generate the desired event class for us. The derivation from the metafactory sidesteps derivation from the concrete class type. The metafactory type gives the unique type discriminator needed to create unique concrete class types.
The questions are:
Is there any "cleaner" or "more desirable" way of doing it? Ideally, the following non-working pseudocode would be my ideal way of doing it - with zero repetition:
class UpdateEvent : public StringEvent <magic>;
The name of the derived class appears only once, and the name of the base concept
StringEvent
appears only once, too. The CRTP requires the class name to appear twice - so far I think it's acceptable, but my metaprogramming-fu is in tatters. Again, I want a preprocessor-less solution, it'd be a no-brainer otherwise.Is the name metafactory my original invention (ha ha), or is it merely my google-Fu that's lacking? This metafactory pattern seems to be quite flexible. It's easy to compose metafactories by multiple derivation. Say you wanted an
Update::Event
made by one factory, andUpdate::Foo
made by another.
This question is motivated by this answer. Note: in real code I'd be using QString
, but I'm trying to keep it as generic as possible.