[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)