I have just released libsigc++ 2.99.1, the first release of the libsigc++-3.0 API, which installs in parallel with the existing libsigc++-2.0 API. This is libsigc++ using much more modern C++, specifically C++14. The API itself is almost completely unchanged, but the implementation is now clearer and easier to contribute to. I’m rather proud of my work, but I’m quite sure that there’s still room for improvement.
Unfortunately, glibmm and gtkmm can’t use libsigc++-3.0 until they have their own parallel-installable versions. That’s not likely to happen until the mythical GTK+ 4 happens. Even though libsigc++ is mostly all templates in headers, its symbols do appear in the linker symbols for gtkmm method signatures, so changes to libsigc++ can break gtkmm ABI. I do have a sigc3 branch of glibmm in git just to show that it works.
With around 150 commits, I gradually refactored libsigc++ to use variadic templates with C++14 instead of generating code from nasty m4 files. Over the years, we have built up quite a large set of regression tests, run during “make check” and “make distcheck”, including various corner cases. Without these tests, the refactoring would have been much harder. With the tests, I could make small commits, knowing that each commit had not broken anything. When something did seem wrong, I could add a test and go back through the git history to find the problem, sometimes splitting commits into even smaller changes. I did a lot of that, rebasing several times, sometimes stopping and starting again after help from Jonathan Wakeley and Marcin Kolny. Those tests give me much more confidence in the end result than I could have if I had chosen to just reimplement the entire API from scratch.
The API is almost all .h files and according to wc, there are 24,145 lines of code in those files in libsigc++ 2.7.1 (after make), and Â 6,507 in libsigc++ 2.99.1. So there is now only 27% as much code.
This is possible because:
- C++ variadic templates allow us to have one class or function where we previously had to generate multiple versions for 1 to 6 function parameters, sometimes with additional versions for const and non-const parameters or const and non-const (and volatile and non-volatile) member function pointers.
- decltype(auto) lets us avoid lots of templated type traits just to correctly specify the correct type for methods.
- The standard C++ type traits, such as std::conditional<>, std::result_of<>, std::is_base_of<>, std::remove_volatile<> and std::is_const<> let us write very generic code, sometimes replacing our own type traits. I added some more to libsigc++ to get compile-time type traits for member method pointers.
- template aliases (like typedefs for templates) avoided the need for multiple functor classes deriving from a common base, even though I ended up not needing most of these aliases either.
- I replaced our sigc::ref() and sigc::reference_wrapper() with std::ref() and std::reference_wrapper<>. Presumably these share a common history.
- I removed some configure checks and ifdef-ed workarounds for older MSVC++ and Sun Forte compilers. Hopefully they aren’t necessary now, but we will see.
For some adaptorsÂ I used the tuple utilities that I’ve been working on recently, for instance in sigc::bind(). These are copied into the libsigc++ source code, and I’d particularly welcome improvements to them in the form of patches or github pull requests.
I’m still not completely happy with all the overloads we have for sigc::mem_fun(), to take member functions that are non-const, const, non-volatile and/or volatile, but I have some things still to try. We might also remove several by not allowing both mem_fun(pointer, func) and mem_fun(reference, func).
Please do suggest ways to simplify the code yet further.
10 thoughts on “libsigc++ 3.0: Very variadic”
Do you have numbers for comparing the binary size after compilation? Is there any overhead to using C++14 features compared to the old code generation tooling?
I was hoping other people would explore that, and the runtime performance, though I’d really like these to be in the git repository so we can track the results across commits.
I’ve just done a quick comparison of the code size of the libsigc++ tests in the 2 branches, with g++ 5.2.1 with -O2. The tests are very slightly larger with libsigc++-3.0 (in git master), but not worryingly so. I wouldn’t be surprised if compilers still have room for more optimization in their code to handle variadic templates.
And I see similar results with clang++-3.8, though the difference there is a little less.
This is a great library but would it be possible to move its development over to Github as its now basically the de-facto open source coding platform, its very easy to use when reviewing code commits and changes.
Thanks. It’s mirrored automatically here:
and I’m inclined to accept pull requests there.
git.gnome.org and bugzilla.gnome.org has served us well so I’m not in a rush to move away from it. However, we do need to stop using sourceforge for the web site hosting. Moving completely to github is an option, but one that some people might object to because github itself is not open source and we have no guarantee that it won’t got the way of sourceforge one day. But I’d be OK with it, I think. Having some contributions via the github mirror would persuade me.
“So there is now only 27% as much code.”
What about the time-duration of the compile? Does it now take longer?
Not noticeably, but I haven’t bothered to time it. If you are interested then you might try timing both the libsigc++-2.0 and libsigc++-3.0 builds. There are several tests which will take up most of the build time, and that’s what would be interesting.
It might be more noticeable on a large project that uses it.
Any changes in thread safety? Last I heard libsigc++ wasn’t thread safe.
Indeed, libsigc++ has never tried to be thread-safe. The earlier developers generally cared a lot about performance, trying to make a signal or slot call as close as possible to a regular function call. Personally, that doesn’t seem like a really important part of the code to optimize. But I guess that’s what people worried about at the time.
Extra mutex locks (or maybe even atomic operations) would slow libsigc++ down, though maybe not so much on newer operating systems.
This post from Herb Sutter does make me feel that libsigc++ should do the locking that can’t obviously be done from outside, much as std:;shared_ptr<> does:
So I’ve made some very initial investigations to do this for libsigc++:
Contributions to that would be very welcome.
However, looking at the code that needs locking, I think I’d like to simplify that code anyway, regardless of thread-safety.
I also need to properly investigate exactly what kind of thread-safety is offered by boost::signals2.
Of course, we cannot pretend that a thread-safe libsigc++ would automatically make use of application objects thread-safe just because they were being used via libsigc++. But I would like us to give useful advice, with examples, about how to do that.
I’d used “function” instead of “method” to stick to the language jargon. “member method” sounds also redundant – doesn’t “method” imply “member” in OOP?