Taming Qt

I like Qt. Qt is a nice framework which allows a lot of stuff to be done. But usually, the nicest stuff is the stuff that is modular and standalone, which can be used easily with a lot of other modules.

My main beef with Qt is all of the non standard C++ stuff which it needs to compile your programs. Meta object compilation, interesting access qualifiers like “private slots:” or “signals:” is not that great. It's as if almost guys in Qt thought that this is THE framework which is all that C++ programmer will ever need.

So, Qt doesn't play nice with standard C++ and is not so easy to interchange with anything else once it permeates your project. If we can't cure it completely, can we at least do something about it?

One idea would be exposing some callbacks. This generally works very well in java/scala combo. Netbeans has nice GUI designer while scala is obviously a lot better language to work with, so, you could design the GUI with netbeans, make it separate jar library, drop the jar into sbt and off you go you can use it with scala.

So, we can have some Qt GUI class, and possibly wrap it around another class where we'd just need to register the callbacks and means to send messages to Qt.

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    ...
private slots:
    ...
// you get the idea.
};

Now, some clean, non “infected” standard C++ class to wrap it:

class MainWindow;

class MainWindowWrapper {
public:
    MainWindowWrapper();

    void setSomeLabel(const std::string& str);
    void setSomeCallback(std::function<void()> func);

private:
    MainWindow* _wnd;
};

So, we exposed two things of the Qt window. We set some label and some callback. The user doesn't have to know Qt to use this class, everything is taken care of in the implementation.

Great! Now, what if we have more buttons and need to expose more?

class MainWindowWrapper {
public:
    MainWindowWrapper();

    void setSomeLabel(const std::string& str);
    void setSomeCallback(std::function<void()> func);

    void setSomeButtonText(const std::string& str);
    void setSomeButtonEnabled(bool value);
    void setAnotherCallback(std::function<void()> func);
private:
    MainWindow* _wnd;
};

And here it starts to grow. For everything we do we need to expose new function. You don't need to be genius to figure it out that this is not easily maintainable approach. People might just ask, why not to just use Qt as THE framework without these silly encapsulation games? And they'd be right. This is not worth it.

Thus the virtual packs in the templatious library were born. Virtual pack is a polymorphic object which contains arbitrary values just like tuple. However, you only need to know a single type signature to use it.

Here's an example:

void doStuff(templatious::VirtualPack& p) { ... }
...

auto p = SF::vpack<int,int>(3,4);

// perfectly legal, true contents
// and type signature is hidden
doStuff(p);

So, we just created a pack, which has signature of int,int and contains 3,4. Surely, if we hide the signature of the pack, how can we use it? Templates don't play well with runtime, do they? Indeed they don't. On the other hand, std::type_index does. Virtual pack saves all the type information as type index. So, we can actually query the pack if it matches that and that signature in doStuff function:

void doStuff(templatious::VirtualPack& p) {
    // nope
    bool matches =
        p.matchesSignature<int,long>();
    assert( !matches );

    // try again
    matches = p.matchesSignature<long,int>();
    assert( !matches );

    // here we match.
    matches = p.matchesSignature<int,int>();
    assert( matches );
}

Surely, not too useful if all we can tell is if our signature matches the virtual pack? We can ask pack to try call a function if the pack matches.

void doStuff(templatious::VirtualPack& p) {
    int outA,outB;
    bool success =
        p.tryCallFunction<int,int>(
            [&](int a,int b) {
                outA = a;
                outB = b;
            }
        );

    assert( success );
    assert( outA == 3 );
    assert( outB == 4 );
}

Here, in tryCallFunction method we are specifying the signature we expect and the lambda to handle it. Now, this part is checked at compile time, meaning that

p.tryCallFunction<char*,std::string>(
    [](int i,long b) {}
)

Is still a compile time error as you'd expect.

Also, virtual packs remember if their args are const or not and are const correct. For example:

auto constPack = SF::vpack<const int,const int>(3,4);

// like evildoers let's try to mutate it
bool success =
    constPack.tryCallFunction<int,int>(
        [](int& a,int& b) {
            a = 7;
            b = 7;
        }
    );

assert( !success );
assert( constPack.fGet<0>() == 3 );
assert( constPack.fGet<1>() == 4 );

The above code is perfectly legal and compiles, however, pack doesn't match a signature since it is not const where original was. We assert that matching didn't succeed (that's the intended behaviour).

Another interesting quirk you might noticed is we call “fGet” method to retrieve the pack values. Method name fGet stands for “fast get”. If you happen to be the original creator of the pack and you know it's true signature, not just the top virtual pack interface it derives, you can call this function to access values in the pack. Function and value to get are resolved at compile time and the types of elements are preserved (for instance, if signature is <char,long>, fGet<0> is char, and fGet<1> is long).

Now, let's mutate the pack:

auto pack = SF::vpack<int,int>(3,4);

bool success =
    pack.tryCallFunction<int,int>(
        [](int& a,int& b) {
            a = 7;
            b = 7;
        }
    );

assert( success );
assert( pack.fGet<0>() == 7 );
assert( pack.fGet<1>() == 7 );

The above succeeds, because original pack had non const signatures and therefore we can bind references to int values there and mutate the pack.

On the other hand, we can bind as const even if the original pack is not const (common sense, eh?):

auto pack = SF::vpack<int,int>(3,4);

int out = 1;
bool success =
    pack.tryCallFunction<const int,const int>(
        [&](const int& a,const int& b) {
            out *= a;
            out *= b;
        }
    );

assert( success );
assert( out == 12 );

We can't match packs partially, everything has to match or the whole pack doesn't, for instance:

auto pack = SF::vpack<const int,const int>(3,4);

int out = 1;
bool success =
    pack.tryCallFunction<const int,int>(
        [&](const int& a,int& b) {
            out *= a;
            b = out;
        }
    );

assert( !success );
assert( out == 1 );

The match above doesn't match for the single 2nd int argument, which wasn't asked as const and the original pack had const.

Matching packs one by one and checking if they succeed is not the greatest solution and can become quite tedious pretty quickly. Templatious has a special class for matching virtual packs, those are called virtual match functors. Let's try one out:

auto packA = SF::vpack<int,char>(1,'1');
auto packB = SF::vpack<char,int>('1',1);

auto mf =
    SF::virtualMatchFunctor(
        SF::virtualMatch<int,char>(
            [](int a,char b) {
                std::cout << "int and char"
                          << std::endl;
            }
        ),
        SF::virtualMatch<char,int>(
            [](char a,int b) {
                std::cout << "char and int"
                          << std::endl;
            }
        )
    );

bool successA = mf.tryMatch( packA );
bool successB = mf.tryMatch( packB );

// prints out:
// int and char
// char and int

assert( successA );
assert( successB );

Now, one match functor can match two virtual packs according to it's types.

Also, the true virtual match functor type with its complex signature can be hidden behind std::unique_ptr:

void someFunct(std::unique_ptr<
    templatious::VirtualMatchFunctor > ptr);

...

// returns std::unique_ptr<
//     templatious::VirtualMatchFunctor >
auto vmfPtr =
    SF::virtualMatchFunctorPtr(
        SF::virtualMatch<int,char>(
            [](int a,char b) {
                std::cout << "int and char"
                          << std::endl;
            }
        ),
        SF::virtualMatch<char,int>(
            [](char a,int b) {
                std::cout << "char and int"
                          << std::endl;
            }
        )
    );

// perfectly legal, true type is hidden
someFunct(std::move(vmfPtr));

Why not std::shared_ptr one might ask? Well, as it happens virtual match functors are composable. And when things are composable, introducing loops with shared pointers is just around the corner. General advice would be if you need a virtual match functor just create a new one.

Let's try out composition of virtual match functors.

typedef std::unique_ptr<
    templatious::VirtualMatchFunctor > VmfPtr;

VmfPtr aHandler() {
    return SF::virtualMatchFunctorPtr(
        SF::virtualMatch<int,char>(
            [](int a,char b) {
                 std::cout << "int and char"
                           << std::endl;
            }
        ),
        SF::virtualMatch<char,int>(
            [](char a,int b) {
                 std::cout << "char and int"
                           << std::endl;
            }
        )
    );
}

VmfPtr bHandler() {
    return SF::virtualMatchFunctorPtr(
        SF::virtualMatch<short,long>(
            [](short a,long b) {
                 std::cout << "short and long"
                           << std::endl;
            }
        ),
        SF::virtualMatch<long,short>(
            [](long a,short b) {
                 std::cout << "long and short"
                           << std::endl;
            }
        )
    );
}

VmfPtr cHandler() {
    return SF::virtualMatchFunctorPtr(
        aHandler(),bHandler()
    );
}

...

auto hndl = cHandler();
auto p = SF::vpack<short,long>(1,2);
bool success = hndl->tryMatch(p);

assert( success );
// printed out:
// short and long

Now, last but certainly not least - dynamic match functor. It's just like previous match functor, however, it is dynamic - you can attach and detach match functors to it as you will at runtime (rather than compile time), assigning priority and possibly altering the order of execution for these match functors, and, it is thread safe. Matching/detaching/attaching virtual match functors is synchronized.

Here's an example:

templatious::DynamicVMatchFunctor dvmf;

int aId = dvmf.attach(
    SF::virtualMatchFunctorPtr(
        SF::virtualMatch<int>(
            [](int a) {
                std::cout << "I am functor A."
                          << std::endl;
            }
        )
    )
);

int bId = dvmf.attach(
    SF::virtualMatchFunctorPtr(
        SF::virtualMatch<int>(
            [](int a) {
                std::cout << "I am functor B."
                          << std::endl;
            }
        )
    )
);

auto p = SF::vpack<int>(7);

dvmf.tryMatch(p);
// prints
// I am functor A
// but not B, because
// if one match matches
// the rest aren't
// processed by default.
// (in broadcast mode
// all would be processed)

// take back b as std::unique_ptr<
// templatious::VirtualMatchFunctor >
auto bFunctBack = dvmf.detach(bId);

// put b back with higher priority
// (default priority is 128)
dvmf.attach(std::move(bFunctBack),129);

dvmf.tryMatch(p);
// prints
// I am functor B
// Now b functor is
// processed first

Now… Do you have any thoughts on how we might deal with Qt signals and slots?

Don't worry if you don't, we're about to do it.

The first thing we'd need to do is poke a hole to send in virtual packs. Let's adjust our MainWindowWrapper we wrote previously.

class MainWindowWrapper {
public:
    MainWindowWrapper();

    void message(VirtualPack& vp);
    void message(std::shared_ptr< VirtualPack > vp);
private:
    MainWindow* _wnd;
};

We threw out all of the functions that were here previously that simply exposed what raw Qt class would with signals and slots.

Two methods are exposed, one which has reference to a virtual pack and another one as std::shared_ptr. We'll not be limited by a single thread for processing our messages, in fact, quite the contrary - virtual packs were built around the idea that they'll be used mainly on multithreaded enviroments.

What happens on the Qt side of things? We'll create a signal that messages these two kind of messages, for smart pointer and for raw reference.

class MainWindow : public QMainWindow {
    Q_OBJECT

public:
    void message(templatious::VirtualPack& p);
    void message(std::shared_ptr<
        templatious::VirtualPack
    > p);

private slots:
    void processMessage(std::shared_ptr<
        templatious::VirtualPack
    > msg);
signals:
    void vMessage(std::shared_ptr<
        templatious::VirtualPack
    > msg);

private:
    void emitToYourself(std::shared_ptr<
        templatious::VirtualPack
    > msg);

    templatious::DynamicVMatchFunctor _dvmf;
};

So, we expose only two methods in this class to be public:

  • message, which takes reference to virtual pack
  • shared pointer to virtual pack

First is for operations that happen in the same thread (the GUI thread) and other can be sent from any thread in the process so the Qt would react. Also, for any callbacks we have dynamic virtual match functor, which is also thread safe.

Now the constructor:

MainWindow::MainWindow() : _dvmf(true) {}

We simply initialize dynamic virtual match functor to true so that it would be created in a broadcast mode to emit all the messages to whosoever will attach.

Now, let's define an interface on how to speak through messages with our modules. And I can almost hear the unsatisfied boo's here - aren't we trying to avoid redefining interfaces from scratch? Yes, we do. However, this interface will be a little different from what we're used to.

MainWindowInterface {
    struct InSetSomeLabel{};
    struct InSetSomeButtonEnabled{};
    struct InAttachHandler{};

    struct QueryLineEditText{};

    struct OutSomeButtonClicked{};
};

Wait, what? Our interface is a bunch of structs? Indeed, we will specify our interface with a bunch of empty structs and specify type signatures in the comments. All we need to play nicely with the virtual match functors is some dummy types to differentiate between them so they could be matched.

Let's document our interface with some comments:

MainWindowInterface {
    // set some label
    // signature: <InSetSomeLabel,const std::string>
    struct InSetSomeLabel{};

    // set some button enabled
    // signature: <InSetSomeButtonEnabled,const bool>
    struct InSetSomeButtonEnabled{};

    // signature: <InAttachHandler,
    //     std::unique_ptr< templatious::VirtualMatchFunctor >
    // >
    struct InAttachHandler{};

    // signature: <QueryLineEditText
    //     std::string <- string to save result to
    // >
    struct QueryLineEditText{};

    // signature: <OutSomeButtonClicked>
    struct OutSomeButtonClicked{};
};

Now, all we need to do is ensure that GUI part handles these messages and in controller part ensure that if we need something from GUI, we emit those messages with the signatures specified in the comments.

We had some awkward wrapping around Qt main window class. Do we really need it? Nope, we don't. All we need is a way to send messages and we should be good. Let's make an abstract interface to accomplish this:

#include <memory>

namespace templatious {
    struct VirtualPack;
}

class Messeagable {
public:
    virtual void message(std::shared_ptr<
        templatious::VirtualPack > msg) = 0;
    virtual void message(
        templatious::VirtualPack& msg) = 0;
};

That's it. This, with the MainWindowInterface struct, is literally all we need to know to be able to use Qt GUI class from our domain. We don't even need to include any templatious headers - forward declaration of virtual pack is enough.

Now, our MainWindow class can simply inherit the abstract interface of Messageable and make sure it handles all the messages specified in MainWIndowInterface struct.

This is how we'd use this in domain side, where we have no idea what Qt is.

void mainWindowInit(std::shared_ptr< Messageable > wnd) {
    typedef MainWindowInterface Msg;
    auto setSomeLabel = SF::vpack<
        Msg::InSetSomeLabel, const std::string
    >(Msg::InSetSomeLabel(), "Set from controller");
    wnd->message(wnd);
}

And on Qt side this could be caught with the following virtual match functor:

std::unique_ptr< templatious::VirtualMatchFunctor >
MainWindow::makeEventHandler() {
    typedef MainWindowInterface Msg;
    return SF::virtualMatchFunctorPtr(
        SF::virtualMatch<
            Msg::InSetSomeLabel, const std::string >(
            [=](Msg::InSetSomeLabel,
                const std::string& str) {
                this->ui->someLabel->setText(
                    str.c_str());
            }
        )
    );
}

All the code will not be shown right here, however, there's a good example written with multiple GUI backends called DecoupledGuiExamples where asynchronous thread is launched and messages are sent across threads to constantly update the GUI while calculating requested prime number.

That's it - all the nonsense, perversions and blasphemies like Q_OBJECT, signals and slots, meta object compilation is none of our concern now when we're in domain. As far as we're concerned we might as well switch to GTK, wxWidgets, Ultimate++ if we wanted - our domain code is completely decoupled from knowing what Qt is (or GTK or wxWidgets or Ultimate++ for that matter). This is probably as loose coupling as it can get (we're specifying message signatures in free form comments for crying out loud!).

Note about performance

So, everything's nice and shiny and flexible with rainbows and unicorns surrounding it. How much does this cost?

The checking done for virtual packs are no magic. The first thing that virtual match/virtual packs does after it's creation is calculate hash. At least on GCC 4.9 default implementation of std::type_index compares strings with strcmp. This is not cache friendly and efficient. Calculating hashes allows for very quick one register comparisons and aggresive inlining by the compiler. However, hash is foolproof only for checking instantly if the value is not what we ask for. To find out if types indeed match up slower std::type_index comparison must be performed.

However, if you have 128 matches that fail and the last one that succeeds, the first 128 most likely will be skipped quickly with quick hash number check and only the last one will be examined thoroughly if it indeed matches.

If performance of virtual pack checking is really that important, general rule of a thumb would be, the more often the event happens the earlier it should be put in virtual match functor.

Less talk, more works. Simple benchmark (which can be copy pasted and compiled really) is down below. This will use the slowest dynamic virtual match functor, which is interlocked, to match single pack million times, where first 128 matches fail, and the last one succeeds.

#include <iostream>
#include <chrono>

#include <templatious/FullPack.hpp>

TEMPLATIOUS_TRIPLET_STD;

int main(int argv,char** argc) {
    templatious::DynamicVMatchFunctor dvmf;

    // add first 128 failing matches
    TEMPLATIOUS_REPEAT( 128 ) {
        dvmf.attach(
            SF::virtualMatchFunctorPtr(
                SF::virtualMatch<char,long>(
                    [](char,long) {}
                )
            )
        );
    }

    // sum something
    volatile long sum = 0;
    // the last good match
    dvmf.attach(SF::virtualMatchFunctorPtr(
        SF::virtualMatch<int,int>(
            [&](int a,int b) {
                sum += a;
                sum += b;
            }
        )
    ));

    auto beg = std::chrono::
        high_resolution_clock::now();
    TEMPLATIOUS_REPEAT( 1000000 ) {
        // heap allocated pack
        // (even though it probably
        // allocates at the same spot)
        auto p = SF::vpackPtr<int,int>(1,2);
        dvmf.tryMatch(*p);
    }
    auto end = std::chrono::
        high_resolution_clock::now();

    int millis = std::chrono::duration_cast<
        std::chrono::milliseconds
    >(end - beg).count();
    std::cout << millis << std::endl;
}

On my laptop with Intel i7-4702MQ CPU @ 2.20GHz processor and clang++-3.7 compiler with -O3 flag this completes on average on 168,57 milliseconds. So, hopefully this is a no brainer whether to use this on GUI callbacks that might happen around 100 times a second on a very busy GUI.