It provides a slightly nicer interface in that signals can be defined in a single template argument, vs. splitting args / return type as separate template arguments. For example:
Qt's approach requires a separate compilation pass woith a Qt-specific tool named "moc" which declares a bunch of info in the signal class that's necessary for the slot implementation.
IMO, the Boost implementation is nicer. It's vanilla C++ (no moc required), is thread safe, and supports some advanced features like "combiners".
I actually prefer QT's approach, it allows you to annotate with extra properties if you want.
Back in games we'd usually use some DSL to generate all the boilerplate code for things like this(also including a whole bunch of editor-specific flags for signals/members).
Also, every time I bring in boost headers I watch compile times crater. C++ 11 or 14 is good enough at this point that I don't see a strong reason to use boost. I know of 2-3 projects that migrated from boost -> C++11 for that reason alone.
I’ve seen a lot of libraries, including parts of Boost, advertised as header-only. To me, this reflects the sad state of building cross-platform C++ dependencies; headers are the only things that library developers are confident that their users will be happy with. I’m very comfortable with doing build system maintenance, so the header-only “feature” seems like a straight-up drawback to me, killing my productivity with longer compile times.
> To me, this reflects the sad state of building cross-platform C++ dependencies; headers are the only things that library developers are confident that their users will be happy with.
...or that the code uses templates extensively (it's boost after all) thus everything must be placed in the same compilation unit so that the code can be generated at compile time.
Even with templates you could split of declaration and implementation. That would help with reducing compile times at the cost of having the user explicitly instantiate the used variations in some central location.
> Even with templates you could split of declaration and implementation.
No, you can't. The only case where the declaration of template functions/classes can be separated from definitions is in the rare cases where only a fixed and predetermined set of objects/values are used, thus enabling explicit instantiation.
And it is not possible to leave that choice to the user? Sometimes I wouldn't mind explicitly instantiating the few variations I need if it meant faster compile times for the remaining files.
> I’m very comfortable with doing build system maintenance, so the header-only “feature” seems like a straight-up drawback to me, killing my productivity with longer compile times.
on linux / macos it's fine, an apt-something away, but on windows anything else than header-only is a pain if you are providing a C++ library, because you may need :
- static and dynamic builds
- debug and release (changes the std:: ABI by default on windows)
- with static (/MT) and dynamic (/MD) runtimes (for whatever reason you can't link /MT with /MD code)
- x86 / amd64 (and more and more people are using windows on ARM increasing the demand for arm and aarch64).
So you need at least 2 * 2 * 2 * 4 (ie 32) distinct builds of your library if you want to cover all cases, and that's ONLY for vs 2015 and 2017 - older and newer versions break binary compatibility ; likewise for mingw / msys which needs its own set of libraries due to using itanium name mangling.
The only reason why this is a problem is because some people are doing this the hard way, manually creating Visual Studio project files for all 32 (or more) configurations. This is wasteful. There are better ways to do this.
You should probably be using tools that generate the Visual Studio projects for you. Some of these tools have been around for a long time, many of them are quite mature. You should be using GYP, CMake, Bazel, or something similar for building. These tools all work on Windows in addition to working on macOS and Linux. Hell, if these tools don’t work for you, then writing a Python script which spits out the Visual Studio projects is not out of the question either… I’ve done it, it’s not terrible!
If you are choosing to make a library header-only just because it’s too hard to build otherwise, well, my request is that you spend a day learning a build system which is not Visual Studio, and see if next time you make a library, you don’t restructure the code just to avoid learning how build systems work.
I mostly use CMake and even in perfect CMake projects it's still a pain to do (since you need all these build folders) - and most projects aren't "perfect" CMake projects, e.g. I'm using portaudio, SDL and their CMake scripts are fairly opinionated and need manual hacking to get required build configurations
I don’t mind modifying the CMake files for my dependencies, or when I’m using Bazel, writing the Bazel build files from scratch. I understand that not everyone feels this way, and it depends on how much labor you can spare for wrangling builds, but I still feel that this is less extreme / preferable to going header-only when header-only is not required for some other reason.
Yes, updating my Bazel build files for third-party dependencies creates extra toil when I upgrade versions. In my experience, this is a small amount of toil compared to the API changes a third-party will introduce (e.g. upgrading libfmt to 5.0 meant touching a hundred files), so I am willing to pay that price to get faster builds.
Or I’d put it this way:
- Most of my time is spent making changes, recompiling, and seeing the results. Small improvements here have a large positive impact on productivity, and header-only libraries make this worse.
- A small amount of my time is spent managing the build system and writing build scripts. Tasks like writing my own Bazel build script for a third-party dependency are not done often, maybe only when porting to a new platform or updating to a new version, so even though I spend extra time here, it is not burdensome.
But once you get CMake to build a VS project with, say, SDL and Lua already linked then it's still much easier than doing it through VS itself every time. The problem is getting it to do that, though.
I don't know why it can't be simpler but apparently it can't.
It's not because of portability, it's more of a language design issue.
Templates and metaprogramming simply doesn't play very well with pre-compiled libraries.
If it were just a template library, it would be fine. But networking libraries? JSON parsers? The problem is much wider. If libfmt can have a couple .cc files so can you.
Sure, that's why I called it a language design issue.
Library users want generic and convenient interfaces (which arguably, boost does very well), there is just no way with modern C++ to keep the nice interfaces and at the same time put a significant amount of logic in a source file.
It is not surprising that boost choose convenience over tradition and fast compile speeds.
Boost signals also handles multiple (or zero) callbacks associated with a single signal. I'm not sure but I believe it also allows calling the slot at a later point from a message pump – certainly Qt's signals and slots do.
A more direct analogue of the type discussed in the article is std::function, which is simply a type that can contain any one callable (function pointer or function object, including lambda functions) or can be empty (like a null function pointer), and can be called in an immediate synchronous way.
> Qt's approach requires a separate compilation pass woith a Qt-specific tool named "moc" which declares a bunch of info in the signal class that's necessary for the slot implementation.
If you are willing to accept uglier macros, you can have QObject-compatible signals / slots in pure C++ without any kind of specific tool : https://github.com/woboq/verdigris
Also, Qt's approach is thread-safe (if you call a signal in a thread A on an object attached to a thread B, the slot will be executed in thread B) since it is tied to Qt's event loop.
We successfully use verdigris in production (image processing application with QT/QML as GUI). This allowed using bazel without having to teach it moc.
Good point about lambdas. I haven't done much C++ since about 2011, which is when lambdas were added to the standard.
In terms of which to use, it depends on your use case.
Lambdas let you easily specify a single callback function, so they're a nice syntactic sugar for passing to STL functions (e.g., for_each). If you're the sender and only have one callback to fire, a lambda is great for this.
If you have to fire n callbacks, you could probably build a signal / slot mechanism with lambdas. But at that point, I'd start to consider using a dedicated library like signals2. It takes care of a bunch of things like object lifetime (e.g., slot deallocation if an object is destructed), efficient management of the list of slots, and thread safety.
> but would it preferable to use Boost for callbacks over using lambdas, assuming you’re on a recent version?
it's not comparable, a boost::signal is equivalent to a list of functions which gets called when the signal is triggered, instead of a single function.
I didn't know Hickey was a C++ hacker. Explains his dim view of OO. Maybe someone should have introduced him to Smalltalk?
Anyway, I find the use of the term "callback" somewhat troubling in the context of OO software. A "callback" is really a procedural concept. In an OO language, it's just a message that you send to one of your arguments, and since message sending is all you can do, the most natural thing in the world.
One example here that's also popular particular in the FRP examples is the button, which needs a "callback". But when you look at a button in Objective-C/Cocoa, it just sends a message to an object. No callbacks in sight, though of course the actual underlying mechanism is very similar.
This turns something like Cocoa into an architectural adapter, adapting the UI into the message-sending architecture of the rest of the program. The user becomes a participant in the program, sending messages to objects in the program by way of the button.
This article can be found in the book "C++ Gems" (I think it originally appeared in the C++ Report). It's interesting from the historical perspective. Rich Hickey is well-known today as the creator of Clojure, but in the 1990ies he was apparently doing C++ development. Back in 1994 when this article was authored, C++ templates were not as well developed as they are today. Beyond generic collection, templates were not much in use. The state of the art for callbacks was a base class with a pure virtual function that you were supposed to implement in a derived class. Most (all?) well knows C++ libraries did it this way. Nobody thought of `std::function` yet. Besides, implementing something like `std::function` using templates of 1994 presented a significant challenge. But Rich Hickey not only recognized the need of such abstraction, but also provided a quite reasonable implementation.
Actually, the fact that C++ has both more and less aesthetically pleasing subsets is a big problem. It means that different people prefer different ways of doing things for purely visual reasons rather than reasons of overall simplicity and maintainability, and that creates needless conflict and churn.
In contrast, Python has very little variation in aesthetic pleasingness. It's all the same Dutch Utilitarian Modernist, perfectly OK if unexciting to look at (@ metaprogramming is an exception.)
And a contrast in the other direction: nobody ever spent a lot of time tweaking their FORTRAN deck for aesthetics.
Just to pick something on my screen at the moment, consider this C++ from mc-stan:
namespace stan {
namespace math {
namespace {
class cos_vari : public op_v_vari {
public:
explicit cos_vari(vari* avi) : op_v_vari(std::cos(avi->val_), avi) {}
void chain() { avi_->adj_ -= adj_ * std::sin(avi_->val_); }
};
} // namespace
there's like 5 things that irritate me about it. So many underscores!
But the big problem is that aesthetic choices interact in complicated ways with architectural decisions. For example, using operator << as syntactic sugar over .add() is motivated by aesthetics, but it affects how you do encapsulation.
A good language lets you choose your architecture, and then write an aesthetically pleasing program to match.
On the contrary, C++ treats user-code aesthetics as a first-class design-decision, moreso than many other languages.
In C++ the whole thing is that built-in types aren't special, including ints and operators. This is a deep concept in the design of the language.
The fact that you can express abstract things like matrix multiplication, compile-time unit-conversions, and compile-time range-checking through operator-overloading speaks to how aesthetic the language is.
> It means that different people prefer different ways of doing things for purely visual reasons
I've never seen any sizable project where visual aesthetics of the API aren't relevant. Java's got chained builders and lambdas, Python's got `@property` and various `__foo__` methods, and any sufficiently large Ruby project's syntax is a DSL wrapped in a riddle wrapped in bacon.
User-defined APIs are syntax, so why limit the tokens?
> A good language lets you choose your architecture, and then write an aesthetically pleasing program to match.
C++ does let you do that. You've pointed to an example where there was little or no intentional thought put into the aesthetics of the API. E.g. `avi_` really could own those methods, and the underscores aren't required by the language.
You can argue that C++ gives you too much power or that sometimes it's non-obvoius how to use that power or that you've seen code where the API designers have taken enough rope to hang themselves. But of course it is possible to write "Java" in C++ and avoid any operator-overloading or syntax-sugar if your team decides it wants to do that.
Yes, it's great that C++ enables you to make aesthetic design decisions. What I dislike is the way they interact in complicated ways with architectural decisions.
Libraries like MTL (the matrix template library) are completely architected around providing their nifty syntax. They do insane things like have operators return types representing a symbolic computation, so that template specialization can be used to do a kind of peephole optimization. The end-user experience is aesthetically improved, but do not stare into the abyss of the template definitions.
The language standard seems to bend over backward to define exceptions for these to that they kinda fit into the system; for example, if you define a variable like this:
SomeType variable;
The default constructor gets called, except if it's a built-in type nothing actually happens and variable has garbage in it.
> In contrast, Python has very little variation in aesthetic pleasingness. It's all the same Dutch Utilitarian Modernist, perfectly OK if unexciting to look at
Really ? Python would probably be the last language I would quote for "One way to do things" and "little aesthetic pleasingness".
There is around 20 ways to do packaging, unit testing, list iteration, string concatenation, list iterations, network calls, Object Oriented and almost any single action in Python.
The Zen of python (https://www.python.org/dev/peps/pep-0020/) might quote "There should be one-- and preferably only one --obvious way to do it.". That never have been the case in practice.
This is false in my experience (more than 10y). Most teams use a linter and a formatter.
The situation is worse in any other language, except maybe golang.
I find working with C++ a struggle against syntax and programmers who know it better than me and working with Java a struggle against boredom (from seeing duplicated code all over the place).
Or you can use std::function. It's fractionally less efficient but much easier to understand and doesn't all have to go in the header (and only needs C++11):
std::function can potentially allocate memory and hides stuff behind a vtable, e.g. two indirections. That's not what I would call "frationally less efficient".
Perhaps "fractional" is the wrong word because has a literal meaning which probably isn't true if you compare calling a std::function vs calling a function directly within a template (although you still need to call that template from somewhere so you'll probably end up with at least one indirection, but I digress).
But as a fraction of your application's overall runtime, in almost cases it's going to be an absolutely insignificant fraction, like less than a millionth of the runtime. This is certainly true if you're writing a GUI or a network-based server process. For it to be noticeable you'd have to be calling the callback in an extraordinarily tight loop, where the actual function does very little (less than a few pointer indirections!).
Put another way, avoiding std::function is a classic case of premature optimisation.
> This is certainly true if you're writing a GUI or a network-based server process.
But are you writing those in C++ ? C++ is used exactly where you have those tight loops. Execution engines, VMs and interpreters... Besides, as soon as you want to have some real-time behaviour you can't have any call to malloc since it may make performance much less deterministic due to locks in malloc implementations. And there is a lot of real-time code being written in C++.
Personally I doubted about the overhead on nowadays machines. I guess a Electron app with a button definitely goes over more abstraction than several indirection when it is clicked. Should we really care about these overhead, at least on desktop platform?
> This mechanism is type safe, achieves the decoupling of Button and CDPlayer, and is good magazine article fodder. It is almost useless in practice, however.
Did you hear that? The sound of the stacked Rust standard library of crates collapsing? Oh the mixinity! Until C++ 20 when concepts come about no static typing solution exists for the issue as C++ templates are duck [template-instantiation time] typed.
Additional context on compile-time, template-instantiation-time and run-time is helpful. In Rust, functions can be given generic parameters, which usually require the generic type to implement a certain trait or traits. Within such a function, the generic value can only be used through those traits. This means that a generic function can be type-checked as soon as it is defined. This is in contrast to C++ templates, which are fundamentally duck typed and cannot be checked until instantiated with concrete types. C++ concepts address the same issue. Thus, given no instantiation cases for template(s) will result in no type checking to be done when compiling those template(s). No instantiation may occur in source-code libraries.
https://www.boost.org/doc/libs/1_68_0/doc/html/signals2.html
It provides a slightly nicer interface in that signals can be defined in a single template argument, vs. splitting args / return type as separate template arguments. For example:
would allow the following function as a slots: There's also Qt's signal/slots implementation: http://doc.qt.io/archives/qt-4.8/signalsandslots.htmlQt's approach requires a separate compilation pass woith a Qt-specific tool named "moc" which declares a bunch of info in the signal class that's necessary for the slot implementation.
IMO, the Boost implementation is nicer. It's vanilla C++ (no moc required), is thread safe, and supports some advanced features like "combiners".