
Callbacks in C++ using template functors (1994) - tosh
http://www.tutok.sk/fastgl/callback.html
======
kenrose
For a more modern form of this, take a look at Boost.Signals2.

[https://www.boost.org/doc/libs/1_68_0/doc/html/signals2.html](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:

    
    
      boost::signals2::signal<void (float, float)> sig;
    

would allow the following function as a slots:

    
    
      void do_something(float x, float y) { ... }
    

There's also Qt's signal/slots implementation:
[http://doc.qt.io/archives/qt-4.8/signalsandslots.html](http://doc.qt.io/archives/qt-4.8/signalsandslots.html)

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

~~~
seandhi
I am asking because I am relearning C++, but would it preferable to use Boost
for callbacks over using lambdas, assuming you’re on a recent version?

~~~
kenrose
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.

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

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

------
patkai
Rich Hickey brought joy and aesthetics back to software development.

~~~
tlb
Aesthetics in C++ is like /* WRITEME: joke * /.

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.

~~~
ryanianian
> Aesthetics in C++ is like /\WRITEME: joke\/.

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.

~~~
saagarjha
> 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.

Except it needs to interact with C, which significantly limits the extent of
this unification.

~~~
gdy
How so?

~~~
saagarjha
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.

------
htfy96
A C++17 way

template<typename CBT> void addListener(CBT cb) {
static_assert(is_invocable_r_v<int, CBT, Button*>, "type mismatch"); }

~~~
quietbritishjim
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):

    
    
        void addListener(std::function<int(Button*)> cb)

~~~
jcelerier
> fractionally less efficient

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

~~~
quietbritishjim
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.

~~~
jcelerier
> 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++.

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

~~~
potiuper
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.

~~~
TheSoftwareGuy
Not sure if you’re joking about the whole post but c++ templates are type
checked at compile time

~~~
potiuper
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.

~~~
maxxxxx
Actually that makes sense. Never thought about it that way.

