11

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:

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

  2. 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, and Update::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.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
  • I don't quite see where the CRTP (i.e., the type `D`) is used in `EventWrapper`. – dyp Oct 04 '13 at 17:56
  • @DyP: It's only used to generate a unique type `EventWrapper`. It serves no other purpose. That's, I hope, the essence of CRTP. – Kuba hasn't forgotten Monica Oct 04 '13 at 17:58
  • @KubaOber So to only call `registerEventType` once *per type*. But then you cannot inherit from the derived Event types, right? (As they had the same id as their base classes.) – dyp Oct 04 '13 at 18:02
  • CRTP doesn't promise uniqueness. It's only unique if you give it a different type as an argument. You could still have 2 different instances of the same type compiled in different compilation units. – Yochai Timmer Oct 04 '13 at 18:05
  • @DyP: That's the whole reason why CRTP is used. The `EventWrapper` is supposed to be used with *most derived type*. That guarantees uniqueness. – Kuba hasn't forgotten Monica Oct 04 '13 at 18:07
  • @YochaiTimmer: Of course the user is expected to come up with unique types for every event, I can't help with *that*. But as long as the most derived type is unique and that type is passed to `EventWrapper`, it works just fine. That's the point of it. – Kuba hasn't forgotten Monica Oct 04 '13 at 18:08
  • @KubaOber Yes, but that implies that `MyEvent1` etc. are final classes. To allow deriving from them (more than once), they had to implement the CRTP themselves, to pass the most-derived type to `EventWrapper`. – dyp Oct 04 '13 at 18:09
  • @DyP: Already mentioned in pre-C++11 way. Note that `unique_t_0` needs to be merely a forward declaration. – Kuba hasn't forgotten Monica Oct 04 '13 at 18:26
  • Title in itself deserves an upvote –  Oct 04 '13 at 19:40

2 Answers2

3

I think what you're looking for might be just using placement new to instantiate the base class.
The derived class won't be constructable because unless they will create a matching constructor.
But, they don't have to be constructable, you could use them anyway. (It could still be destructable).

template <class T>
class Base
{
protected: Base(int blah) { }

public: static T* CreateInstance(int data) { 
            T* newOjectBlock =  reinterpret_cast<T*>(::operator new(sizeof(T))); // allocate enough memory for the derived class
            Base* newBasePlace = (Base*)(newOjectBlock); //point to the part that is reseved for the base class
            newBasePlace=  new ((char*)newBasePlace) Base(data); //call the placement new constrcutor for the base class
            return newOjectBlock;
        }
};

class Derived : public Base<Derived> {}

Then let the CRTP base class construct the derived class like this:

Derived* blah =  Derived::CreateInstance(666);

If anyone ever wants to initialize the derived class, they should either make a matching constructor that calls the base class constructor.
OR, just make an .init() method that initiates its members, and will be called after the instance is created.

OR, we can think of something else, this is just an idea of a concept.

Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
  • Is it legal to call `delete` on an object created this way? I thought if you used placement new you had to call the destructor yourself. I guess you could create another static function `DestroyInstance(T * p){ p->~T(); delete[] reinterpret_cast(p);}` to handle that. – SirGuy Oct 04 '13 at 19:04
  • @GuyGeer: Calling `delete` on something allocated with `new[]` is not okay. Placement `new` on a memory block allocated with global `new` should work just fine with global `delete`, though. The rule about them being incompatible is if you use placement `new` on some other kind of memory, say the stack, since global `delete` both calls the destructor and tries to free the memory block (which it assumes came from global `new`). – Sean Middleditch Oct 04 '13 at 19:17
  • @KubaOber In this way you don't need to know the base class at all. You just use the scope of your derived class. – Yochai Timmer Oct 04 '13 at 20:47
  • @SeanMiddleditch invoking the destructor won't free the memory. you need to call the delete afterward like Guy said. – Yochai Timmer Oct 04 '13 at 21:03
  • @YochaiTimmer: I'd appreciate seeing some complete code to show how it solves my problem, because so far I don't see how it helps :( It may be a problem with me more than with the answer, so just humor me please. Well, OK, now I see which part of the problem it solves, but it's still somehow dirty-feeling. – Kuba hasn't forgotten Monica Oct 04 '13 at 21:06
  • @YochaiTimmer: Never mind that you're creating a half-constructed object and returning it using a pointer to the derived class. That's not useful at all. It's plain old undefined behavior. It doesn't solve any problems, it adds to them. I don't know if the standard guarantees that the trivial derived class adds no data... – Kuba hasn't forgotten Monica Oct 04 '13 at 21:08
  • @YochaiTimmer: I never said otherwise. Your use of `new[]` without using `delete[]` is wrong, though. Use `::operator new(sizeof(T))` instead if you expect regular non-array `delete` to be used for deallocation. – Sean Middleditch Oct 04 '13 at 21:13
  • @KubaOber a big difference in this approach is that you allocate memory for the derived class, and you end up with an instance of a derived class object. While your original solution would allocate an object of the base class type. So if the derived class has extra members, they wouldn't have been allocated at all. – Yochai Timmer Oct 05 '13 at 07:11
3

Yochai Timmer has come up with an alternative way of approaching the problem. Instead of having to forward the constructor from the data carrier class, he exposes a factory method that produces pseudo-derived classes. As it invokes undefined behavior, I'm not particularly keen on it.

Expanding a bit on the original metafactory concept, it's possible to make generic metafactory that can be used to make unique event types that wrap "any" data-carrying class.

The approach for C++11 uses constructor forwarding so that plain non-template data carrier classes can be used. The approach for C++98 requires a templated data carrier class and, internally, a bit more gymnastics, but it works as well.

The event classes can't be further derived from. This is necessary since the derived classes would all share the value of staticType, and that can't be allowed, as DyP duly noted in the comments.

To test the code, you need the event wrapper, the metafactory and data carrier selected for your variant of C++, and the test/usage part.

The Event Wrapper (Common Code)

In either case, our basic event wrapper CRTP class that generates a unique static type value for the event is:

// A type-identifier-generating wrapper for events. It also works with RTTI disabled.
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;
    }
    static bool is(const QEvent * ev) { return ev->type() == staticType(); }
    static Derived* cast(QEvent * ev) { return is(ev) ? static_cast<Derived*>(ev) : 0; }
};

Note that it also provides a cast-to-derived method. You'd use it in an event handler, given a pointer to a base event class:

void MyClass::customEvent(QEvent* event) {
   if (MyEvent::is(event)) {
      auto myEvent = MyEvent::cast(event);
      // use myEvent to access data carrying members etc)
   }
}

The C++98 Metafactory

The Carrier is a parametrized data carrier class, such as StringData below.

// The generic event metafactory
template <typename Derived, template <typename> class Carrier> class EventMF {
    class EventFwd;
    class Final;
    class FinalWrapper : public EventWrapper<EventFwd>, public virtual Final {};
public:
    // EventFwd is a class derived from Event. The EventWrapper's cast()
    // will cast to a covariant return type - the derived class. That's OK.
    typedef Carrier<FinalWrapper> Event;
private:
    class EventFwd : public Event {};
    class Final {
        friend class FinalWrapper;
        friend class Carrier<FinalWrapper>;
    private:
        Final() {}
        Final(const Final &) {}
    };
};

The EventFwd class is needed so that we have something sane to pass to the EventWrapper template as the derived class, so that the cast() static method will work. The FinalWrapper is there since in pre-C++11 we can't friend typecasts.

Now for the parametrized data carrier. It'd be the same as for the C++11 variant below except for needing to have a parametrized base class.

// A string carrier
template <typename Base> class StringData : public Base {
    QString m_str;
public:
    explicit StringData(const QString & str) : m_str(str) {}
    QString value() const { return m_str; }
};

The C++11 MetaFactory

// The generic metafactory for unique event types that carry data
template <typename Derived, class Data> class EventMF {
    class Final;
    EventMF();
    EventMF(const EventMF &);
    ~EventMF();
public:
    class Event : public EventWrapper<Event>, public Data, private virtual Final {
    public:
        template<typename... Args>
        Event(Args&&... args): Data(std::forward<Args>(args)...) {}
    };
private:
    class Final {
        friend class Event;
    private:
        Final() {}
        Final(const Final &) {}
    };
};

The gymanstics with forward-declaration of the Final class are there since forward-declaring the Event class is more typing.

The data carrier is as simple as it gets:

// A string carrier
class StringData {
    QString m_str;
public:
    explicit StringData(const QString & str) : m_str(str) {}
    QString value() const { return m_str; }
};

Usage & Tests (Common Code)

And now we can use the generic metafactory to make some concrete metafactories, and then to make the event classes we need. We create two unique event types that carry the data. Those event classes have unique staticType()s.

// A string event metafactory
template <typename Derived> class StringEventMF : public EventMF<Derived, StringData> {};

class Update : public EventMF<Update, StringData> {}; // using generic metafactory
class Clear : public StringEventMF<Clear> {}; // using specific metafactory
#if 0
// This should fail at compile time as such derivation would produce classes with
// duplicate event types. That's what the Final class was for in the matafactory.
class Error : public Update::Event { Error() : Update::Event("") {} };
#endif

int main(int, char**)
{
    // Test that it works as expected.
    Update::Event update("update");
    Clear::Event clear("clear");
    Q_ASSERT(Update::Event::staticType() != Clear::Event::staticType());
    Q_ASSERT(Update::Event::staticType() == Update::Event::cast(&update)->staticType());
    qDebug() << Update::Event::cast(&update)->value();
    Q_ASSERT(Update::Event::cast(&clear) == 0);
    qDebug() << Clear::Event::cast(&clear)->value();
    Q_ASSERT(Clear::Event::cast(&update) == 0);
}
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313