[HN Gopher] Callbacks in C++ using template functors (1994)
___________________________________________________________________
Callbacks in C++ using template functors (1994)
Author : zengid
Score : 32 points
Date : 2025-10-05 17:50 UTC (5 hours ago)
(HTM) web link (www.tutok.sk)
(TXT) w3m dump (www.tutok.sk)
| zengid wrote:
| Heard about this watching Casey Muratori's "The Big OOPs" talk
| [0]. Thought it couldn't be _that_ Hickey, but turns out it was!
|
| [0] https://youtu.be/wo84LFzx5nI?si=SBv1UqgtKJ1BH3Cw&t=5159
| drnick1 wrote:
| C++ was so much cleaner in the 90s, when it was still essentially
| "C with classes," which is how I like to use the language. Modern
| standards have turned it into an ugly mess.
| drob518 wrote:
| Amen. The syntax just kept getting more and more complicated. I
| gave up in the late 1990s. Ironically for this post, I now
| prefer to write everything in Clojure. It seems like my own
| journey has paralleled Rich's journey. Maybe that's why I
| appreciate so many of the design choices in Clojure. It's not
| perfect, but it's really, really good.
| lbalazscs wrote:
| A sentence from the article: "Given the extreme undesirability
| of any new language features I'd hardly propose bound-pointers
| now."
|
| It shows that C++ was considered too complex already in the
| 90s.
| codr7 wrote:
| And then they introduced coke at committee meetings, the
| crazy shit they've been coming up with lately shows
| absolutely zero understanding of the complexity issue.
| jcelerier wrote:
| and yet client code can be incredibly simpler nowadays
| thanks to all these features.
|
| I can write this simple struct: struct
| Person { std::string name;
| my_complicated_date_type date_of_birth; };
|
| and get serialization, network interop, logging, automated
| UI generation, hashing, type-safe IDs, etc. without having
| to write an additional line of code. Twenty years ago you
| had to write 2000 lines of additional boilerplate for each
| data type to get to the same place.
| jstimpfle wrote:
| You would have added some codegen, should be possible to
| write a codegen framework from scratch in 2000 lines
| even.
|
| Arguably the result would have been easier to read and
| maintain and not as slow to compile.
| pton_xd wrote:
| I also use C++ as "C with classes," however I will concede that
| many of the modern C++ additions, particularly around
| templating, are extremely convenient. If you haven't had a
| chance to use requires, concepts, "using" aliases, etc I'd
| recommend giving them a try. I don't reach for those tools
| often, but when I do, they're way nicer than whatever this
| article is demonstrating from 1994! Oh yeah, also lambdas,
| those are awesome.
| MomsAVoxell wrote:
| >ugly mess
|
| That may be the case, but there are plenty of examples of
| elegant implementations.
|
| JUCE, for instance: #include
| <juce_core/juce_core.h> class MyComponent {
| public: void
| doAsyncOperation(std::function<void(int)> callback) {
| // Simulate async work
| juce::MessageManager::callAsync([callback]() {
| callback(42); // Call the functor with result
| }); } }; // Usage
| MyComponent comp; comp.doAsyncOperation([](int result)
| { juce::Logger::writeToLog("Callback received with:
| " + juce::String(result)); });
|
| .. I think that's kind of clean and readable, but ymmv, I
| guess?
| asveikau wrote:
| I dunno, I skimmed the article's 31 year old code examples and
| immediately thought they would be shorter and simpler in c++11
| or later.
|
| But it's important to see the 1994 (and 1998) view of the world
| to understand how modern c++ features work. Because they start
| from that worldview and start adding convenient stuff. If you
| don't understand how c++ used to work, you may be confused with
| why c++ lambdas look so weird.
| adzm wrote:
| You can just use the parts you want though; that's part of its
| appeal.
| eschaton wrote:
| This is a thing C++ advocates say that tells me they've never
| really tried to do it _and_ share that codebase with others
| or integrate with other codebases.
|
| You generally don't get to pick what parts other people want
| to use, which means that in the end you still have to deal
| with the entirety of the language.
| codr7 wrote:
| Exactly, it doesn't work very well in practice.
|
| Even when working alone, the complexity gradually creeps up
| on you.
|
| Because it's all made to work together, start pulling
| anywhere and before you know it you're using another
| feature, and another, and so on.
|
| And many features interact in exotic and hard to predict
| ways, so hard that entire careers have been spent on trying
| and failing to master the language.
| spacechild1 wrote:
| No member function templates, no variadic templates, no
| std::function, no lambdas, etc. That's certainly not the kind
| of C++ I would want to write...
| mwkaufma wrote:
| Red flags for me when I see nonstandard functors in a c++
| codebase (esp if the "glue" is in a setup function independent of
| the objects):
|
| (i) Have they thought about the relative lifetimes of the sender
| and receiver?
|
| (ii) Is the callback a "critical section" where certain side-
| effects have undefined behavior?
|
| (iii) Does the functors store debugging info that .natvis can
| use?
|
| (iv) Is it reeeeeeeally that bad to just implement an interface?
| tcbawo wrote:
| Can you elaborate on your third point? What would a class need
| to do to affect debugging info?
|
| Regarding your fourth point, sometimes an architecture can be
| vastly simplified if the source of information can abstracted
| away. For example, invoking a callback from a TCP client, batch
| replay service, unit test, etc. Sometimes object oriented
| design gets in the way.
|
| To your first point, I think RAII and architecture primarily
| address this. I'm not sure that I see callback implementation
| driving this. Although I have seen cancellable callbacks,
| allowing the receiver to safely cancel a callback when it goes
| away.
| mwkaufma wrote:
| >> Can you elaborate on your third point? What would a class
| need to do to affect debugging info?
|
| Common implementations are a function pointer + void* pair,
| which in most debuggers just show you two opaque addresses.
| Better to include a info block -- at least in debug builds --
| with polymorphic type pointers that can actually deduce the
| type and show you all the fields of the receiver.
|
| >> sometimes an architecture can be vastly simplified if the
| source of information can abstracted away.
|
| "sometimes" is doing a lot of heavy lifting here. That's my
| whole point -- more often than not I see some type of
| homespun functor used in cases that are _not_ simplified, but
| actually complicated by the unnecessary "plumbing."
|
| >> RAII and architecture primarily address this
|
| If the receiver uses RAII to clean up the callback, then
| you've reintroduced the "type-intrusiveness" that functors
| are meant to avoid...?
| tcbawo wrote:
| > most debuggers just show you two opaque addresses
|
| This has not been my experience. But I haven't needed to
| deal with RTTI disabled.
|
| By RAII, I mean using destructors to unregister a callback.
| This covers 99.9% of use cases. Generally callback
| registration is not where you really want type erasure
| anyways.
| mwkaufma wrote:
| >> By RAII, I mean using destructors to unregister a
| callback.
|
| _Whose_ destructor, if not the receiving-type? Is there a
| third "binding" object, because then you have three
| potentially-unrelated lifetimes.
|
| >> Generally callback registration is not where you
| really want type erasure anyways.
|
| I'm responding to the article: "Some mechanisms for doing
| callbacks require a modification to, or derivation of,
| the caller or callee types. The fact that an object is
| connected to another object in a particular application
| often has nothing to do with its type. As we'll see
| below, mechanisms that are type intrusive can reduce the
| flexibility and increase the complexity of application
| code. "
| tcbawo wrote:
| > Whose_ destructor, if not the receiving-type
|
| The receiving type should control the lifetime of any
| callbacks to itself that it gives away. The destructor is
| the best place to ensure this gets properly cleaned up.
|
| Like anything, custom callbacks can be used well or
| misused. Design is a matter of expertise and taste
| bordering on an art form. Connecting framework
| implementation and business logic can be done cleanly or
| clumsily. I am skeptical of an argument that callbacks
| have a code smell prima facie.
| mwkaufma wrote:
| I don't disagree, but the article does. RH describes an
| architecture where setup functions create callbacks,
| independent of the receiving type. If I were to steelman
| him, it would be something like this: "in a pedantic MVC
| system, model objects don't depend on view objects by
| design, and therefore should not be aware that their
| methods are used as 'click callbacks'"
| kazinator wrote:
| > _Red flags for me when I see nonstandard functors in a c++
| codebase_
|
| Even if it's 1994???
| dang wrote:
| Related. Others?
|
| _Callbacks in C++ using template functors (1994)_ -
| https://news.ycombinator.com/item?id=18650902 - Dec 2018 (50
| comments)
|
| _Callbacks in C++ using template functors - Rich Hickey (1994)_
| - https://news.ycombinator.com/item?id=12401400 - Aug 2016 (1
| comment)
|
| _Callbacks in C++ using template functors (1994)_ -
| https://news.ycombinator.com/item?id=10410864 - Oct 2015 (2
| comments)
| zengid wrote:
| not sure why the title was renamed, but i thought this was
| interesting primarily because it's the early work of Rich Hickey,
| famous for making the Clojure language.
___________________________________________________________________
(page generated 2025-10-05 23:00 UTC)