New year, new career, time to come full circle with some old ideas...
It's a new year, and for the first time since 2003 I've accepted a job offer at a place that includes zero C++ programming.Since this is the last I'll be using C++ or Qt in the near future (though, who knows...) I thought I'd share a thingie I've developed during the last few years.
It is not really a library or that much code... it is more of a style, an idea, an idiom, a drizzle of syntactic sugar.
This idea actually is quite simple so it may exist in the wild somewhere but I did try to google it and couldn't find something similar. So either I didn't Google the right keywords or it's a novel/bad idea.
What made me start thinking of this
It was a conversation while working on Qt at Nokia during the inception of QtQuick, back then "Declarative UI" and other names. I was a software engineer working with the Qt business development group, and remember a single quote from one of the other engineers that stayed with me:
C++ is imperative.I had a response in my head at the time that I hadn't voiced because I couldn't quite actualize it yet:
But what if it was?Time has passed and QtQuick grew to be a decent and legitimate set of tools for touch/device UI. However, it always had two built-in limitations (which don't take away from its strengths):
- QML is not compiled, thus connecting it with other C++ code requires a lot of broilerplate shim code (QObjecty stuff)
- QML is very tied to the Qt framework
I wanted to see how declarative programming can be used in C++, but inside the language and not with an additional interpreter which requires language bindings etc.
So what is declarative really?
Since conceiving and contributing to the Qt state machine framework together with the clever folks at Nokia/Trolltech, I have thought state machines were an interesting case for declarative programming. Taking them as a test case, I can define declarative programming as:
Setup of a hierarchical data structure that interfaces with the programmatic modules.Hierarchical statecharts are a tree data structure, and it interfaces with the other modules - e.g. you want to change colors of a widget when a state is entered/exited.
So if we temporarily reduce "declarative" to defining "Tree-structured data that interfaces with code", it is clear why the old C++ could not easily be used for declarative programming. Defining tree data structures in the old C++ is very ugly (look at code that interfaces with the boost state machine frameworks for examples), and setting up a decent QStateMachine in C++ requires a lot of imperative "setThis" "setThat" lines.
Looking at what actually happens inside QML (take the rest of QtQuick aside for a sec), it converts a tree structure to a a set of commands to set properties.
Delcaraitve is really about converting a data structure to a live tree with a setup phase.
Along came C++11
I was able to use C++11 in various projects I've been working on since mid 2013, when compiler support caught up with the particular product/platform roadmaps.
When I started looking into its details, between my two previous jobs, I found it had several key features that made me feel declarative programming was not just possible but potentially sweet:
- Variadic templates
- Move constructors
- decltype
- Lambdas
Looks a bit like QML, but not really
The idea was to take the nice bits about QML - mainly how the code looks and feels when constructing a tree data structure, and assimilate it into the C++ world.
In other words, build a curly-bracket tree representing hierarchical data inside C++.
The code for this, in C++, looks like this:
Object1 {
Object1 { Flag1, Property1{0}, Property2 {"string"},
Object2 { }
},
Object2 { InlineFunction1 { [=] { doSomethingInLambda(); } }
}
When described in words, what I do is this:
- Use a variadic constructor as a way to declare a tree structure for an object (e.g. Object1)
- Define special struct types (e.g. Property1) as a way to define properties
- Define special enum types (e.g. Flag1) as a way to define flags.
To make this work, the different property, flag and object types need to be defined beforehand:
// Define properties as structs
struct Property1 { int n; }
struct Property2 { std::string s; }
struct InlineFunction1 { std::function<void()> f; }
// Define flags as anonymous enums
enum { Flag1 };
Then we define object types, like this:
class Object1Base {
protected:
void declare(Flag1);
void declare(Property1);
void declare(Property2);
void declare(InlineFunction);
void declare(Object2);
};
class Object2Base {
public:
};
typedef Declarative<Object1Base> Object1;
typedef Declarative<Object2Base> Object2;
The "Declarative" template class is the little magic (really rudimentary variadic args) that binds a declarative constructor like the example to the different "declare" functions.
The clarat code & back to QStates
To demonstrate how this ties back together, I've prepared a little header file that does the declarative constructor magic, and created, as an example, a binding to QStateMachine.
The code for a state machine, that would normally look like a bunch of setThis/setThat now looks like this:
State {
State { Initial, Transition { Target { &test1 } } },
State { &test1,
State { Initial, Transition { Target { &test1Sub1 } } },
OnEntry { [=] { printf("Inside Test1\n"); } },
State { &test1Sub1,
OnEntry { [=] { printf("Inside Test1Sub1\n"); } },
OnExit { [=] { printf("Leaving Test1Sub1\n"); } },
Transition { On<Event1>(), Target { &test1Sub2 } }
},
Final { &test1Sub2 },
Transition { OnDone, Target { &test2 } }
},
State { &test2,
OnEntry { [=] { printf("Inside Test2\n"); qApp->exit(0); } }
}
}
Isn't this much more manageable and tasty than writing a bunch of setter functions?
It was for me but maybe it's an acquired taste.
The full example is available freely at https://github.com/noamr/clarat. It works with the QStateMachine framework but the declarative aspect is not Qt-specific.
Summary
I've used code like this (not exactly this code, I've written this one now...) in production. It had helped me and the team maintain C++ UI/states setup code that usually becomes rather convoluted, and keep it clean and easily changeable.
I probably forgot to mention something extremely important, so please mention that one in the comments.
My biggest satisfaction would be from seeing this syntactic sugar being used out there in the world, making C++ setup code slightly nicer.
~Cheers
Noam