[HN Gopher] Simplest C++ Callback, from SumatraPDF
___________________________________________________________________
Simplest C++ Callback, from SumatraPDF
Author : jandeboevrie
Score : 158 points
Date : 2025-06-15 17:26 UTC (1 days ago)
(HTM) web link (blog.kowalczyk.info)
(TXT) w3m dump (blog.kowalczyk.info)
| not-so-darkstar wrote:
| https://web.archive.org/web/20250615181542/https://blog.kowa...
| kjksf wrote:
| It's back up now.
|
| Somehow my blog server got overwhelmed and requests started
| taking tens of seconds. Which is strange because typically it's
| under 100ms (it's just executing a Go template).
|
| It's not a CPU issues so there must be locking issue I don't
| understand.
| waynecochran wrote:
| A small kitten dies every time C++ is used like its 1995.
| void (*fn)(void*, T) = nullptr;
| plq wrote:
| Unless you mutter the magic incantation "C compatibility" while
| doing it
| waynecochran wrote:
| At this point, C++ has long abandoned C at the language
| level. If you are going to write C code, write C code --
| don't hamstring modern C++.
| zabzonk wrote:
| did nullptr exist in c++ back in 1995 - i can't remember
| trealira wrote:
| Nope, it was introduced in C++11, along with the type
| std::nullptr_t. Before that, you either used 0 or NULL, which
| was a macro constant defined to be 0.
| waynecochran wrote:
| That is the only thing "modern" in the above code and it
| does not save the kitten.
| tom_ wrote:
| And another one dies every time you need to step through a call
| to std::function. Whatever you do, the kittens are never going
| to escape.
| waynecochran wrote:
| To save the kitten, one could at least write classes that
| override the call operator::(). No need for void* or any ptrs
| at all and std::function is avoided as well. That will at
| least get you to 2010 C++ code.
| JeanMarcS wrote:
| Don't know about the code subtilities, but SumatraPDF is a gift
| for viewing PDF on MS Windows. So big thanks to the author !
| Arainach wrote:
| Out of curiosity, what's your use case for it? Years ago I
| preferred Sumatra/Foxit to Adobe, but every major browser has
| supported rendering PDFs for at least a decade and I haven't
| had needed or wanted a dedicated PDF reader in all that time.
| mjmas wrote:
| Part of why I use SumatraPDF is that it automatically reloads
| its view when the files change (at least for PDFs, I haven't
| tested on the other file types it supports).
| KaushikR2 wrote:
| That's not always desirable though. I'd rather have control
| over that
| eviks wrote:
| You have, via a config. You have no control in a browser.
| jasonfarnon wrote:
| same here--I can't imagine using latex without this
| feature. To me it's a beautiful piece of software, the only
| thing I keep pinned to the taskbar other than wsl shell.
| Cadwhisker wrote:
| It's smaller, lighter and much faster than launching a web
| browser to view a PDF. I can configure it to open a new
| instance for each PDF which is nice if you need to have
| several docs open at once. Again, nothing that you can't do
| with a browser and dragging tabs, but I prefer this.
| drewbitt wrote:
| Not only is it faster in opening than a browser and a
| separation of concerns (documents get their own app, which I
| can leave with open tabs), it also opens epub, .cbz, and
| other formats, so I have it installed on all my Windows
| machines. I eventually open a book.
| ternaryoperator wrote:
| Not the OP, but my use case is epub books, which it handles
| flawlessly.
| graemep wrote:
| Large PDFs are very slow in browsers. I believe they all use
| pdf.js (or similar).
| cAtte_ wrote:
| firefox uses pdf.js, but chromium uses pdfium and safari
| uses pdfkit
| graemep wrote:
| PDFkit seems similar in that it is JS. Is it also slow?
|
| I just tried a few PDFs in Chromium and PDFium seems to
| be much better than pdf.js - faster and handles forms
| more smoothly.
| fredoralive wrote:
| PDFkit seems to be a name a few different things in stuff
| like node and ruby. I think the Apple PDFkit is probably
| just wrapping Apple's in house PDF tech that Preview
| uses?
| graemep wrote:
| It does not seem to have any requirements than JS -
| browser or Node. There is an online demo that works with
| Firefox on Linux so not wrapping anything else.
| cAtte_ wrote:
| the pdfkit from the first google result doesn't seem to
| be related to apple's. what happened here is that
| "pdfkit" is a very generic name (that will tend to show
| up because people love writing pdf-related software) that
| also happens to coincide with apple's convention of
| naming their frameworks something-kit (uikit, appkit,
| avkit, ...)
| vachina wrote:
| > use case
|
| Sumatra excels at read-only. Usually anything to do with PDF
| is synonymous with slow, bloat, buggy, but Sumatra at just
| 10Mbytes, managed to feel snappy, fast like a win32 native
| UI.
| sameerds wrote:
| Opening a pdf inside a browser feels to me like an
| application inside an application. My brain can't handle that
| load. I would rather have the browser to browse the internet
| and a pdf reader to display pdfs. If I clicked on a link to a
| pdf, it is _not_ part of the web, and I want the browser to
| stay out of it. Same goes for Office 360 wanting to documents
| inside my browser. I don't want it to do that. I have the
| necessary apps installed for it.
| 4gotunameagain wrote:
| I really would not like to ruin the entire internet for
| you, but isn't the vast majority of websites these days
| fully fledged applications, hence applications in
| application in the sense you mentioned ?
|
| I would argue that a pdf reader is much simpler than
| multiple very popular webpages nowadays.
| smaudet wrote:
| > the vast majority of websites these days fully fledged
| applications
|
| Many apps do exist on the web. Not many of them are very
| good, PDF is a bit case in point - web struggles to fully
| implement PDF read/write (partly a complexity thing,
| partly I'm sure a tactic non-compete with Adobe).
|
| Even more sites still exist that aren't apps, even if a
| few big sites have stolen people's imaginations...
|
| So two reasons why you don't really want your PDF in the
| browser (I don't mind much if I'm never going to look at
| it again but otherwise no I don't want it in my browser).
|
| Even if the first two weren't true, there's still just
| the fact that, no, PDF is local, web is not. I don't need
| an internet connection for one, and I don't need to worry
| about one messing with the other. Sounds like a strange
| thing to worry about, but browsers do crash, and more
| importantly browsers are often filled with tabs. You can
| have many PDFs open but you don't need to keep them all
| in memory...
| eviks wrote:
| How do you alt tab to a browser tab with a PDF? How do you
| change navigation shortcuts when browsers are notoriously bad
| at such customizations?
| agent327 wrote:
| Sumatra will reload any PDF that changes while you are
| viewing it (Adobe locks the file, so you can't change it to
| begin with). This is incredibly useful when you are writing
| documentation using a document generating system (like
| docbook).
| NooneAtAll3 wrote:
| in my experience, browser pdf viewers take a loooot more RAM
| than Sumatra
| df0b9f169d54 wrote:
| As I still recalled it's possible to configure an external
| editor so that when you click on any place on sumatraPDF
| viewer you can open the source file that is annotated with
| the clicked position. This is extremely helpful when working
| with LaTeX documents.
| dolmen wrote:
| It is not sandboxed.
|
| So one can expect zero day exists and are exploited.
|
| That may not be a feature for you, but it is for attackers.
| jasonjayr wrote:
| Does it implement any of the dynamic features in PDF that
| are vectors for easy attacks like that?
|
| PDF _was_ originally a display-only format.
| kccqzy wrote:
| You don't need any dynamic features in PDF to attack. One
| of the most famous exploits used a bug in the JBIG2
| format to build the attacker's own dynamic feature
| (basically a virtual machine built from logic operations)
| to launch an exploit.
| https://googleprojectzero.blogspot.com/2021/12/a-deep-
| dive-i...
|
| In fact you have gotten it backwards. The obviously
| dynamic features in PDF like JavaScript are designed to
| be dynamic so they receive so much more attention in
| security. So smart attackers attack the not-obviously-
| dynamic features in PDF.
| jasonjayr wrote:
| Ah, very good point.
| shakna wrote:
| Sumatra has more security features than most other readers?
|
| For example, it doesn't support JavaScript. And it doesn't
| support GoToE.
|
| The text features, both strings and fonts, get sent through
| HarfBuzz for sanitisation.
|
| How is it not sandboxed?
| vgb2k18 wrote:
| If you hate it when pdfs won't print because of restrictive
| permissions... Sumatra.
| lenkite wrote:
| > I haven't had needed or wanted a dedicated PDF reader in
| all that time.
|
| OK. Now load 100 PDF's. You will need a dedicated PDF reader
| unless you don't mind wasting a truckload of RAM. Also,
| browser PDF readers are generally slower and are not optimal
| at search/bookmarks/navigation/etc.
| CamouflagedKiwi wrote:
| I've never needed to load 100 PDFs at once, and honestly I
| don't imagine I ever will. I guess it might happen for some
| people, so a dedicated app would be useful for them.
|
| For me, having a separate dedicated app isn't worth it for
| the benefits you mention, which to me are minor compared to
| having to install and manage another thing (which, to be
| fair, I imagine Sumatra to be a very pleasant citizen at
| compared to Acrobat).
| smaudet wrote:
| Even just 10 PDFs is leaner than a similar number of
| bloated web pages.
|
| Documentation particularly is a big one. Then there are
| books, etc.
|
| Some web pages can still manage to be leaner than a PDF,
| but as another poster pointed out, a lot of modern web is
| pretty trash...
| wavemode wrote:
| opening (and browsing/searching through) a very large PDF is
| a nightmare in most browsers
| jitans wrote:
| Why don't use fu2? https://naios.github.io/function2/
| kjksf wrote:
| One, I didn't know about it.
|
| Two, my main objective is extreme simplicity and
| understandability of the code.
|
| I explicitly gave up features of std::function for smaller code
| that I actually understand.
|
| fu2 seems to be "std::function but more features".
| mdaniel wrote:
| SumatraPDF is outstanding software. But I'm actually surprised to
| hear that it seems to be written in C++ ... I dunno, kind of like
| "by default?" And a blog post hand rolling callback functions
| using structs and a bunch of pointers seems to double down on:
| are you sure this language is getting you where you want to go?
| kjksf wrote:
| As opposed to?
|
| Today, if I was starting from scratch, I would try zig or odin
| or maybe even Go.
|
| But SumatraPDF started 15 years. There was nothing but C++. And
| a much different C++ that C++ of today.
|
| Plus, while my own code is over 100k lines, external C / C++
| libraries are multiple of that so (easy) integration with C /
| C++ code is a must.
| mdaniel wrote:
| I didn't know how to correctly package my comment as not
| criticizing, and that's half of why I opened with "is
| outstanding software." I genuinely believe that, I'm deeply
| grateful for you releasing SumatraPDF into the world, and it
| makes my life better. Truly, I am thankful
|
| I hear you about "back in my day," but since as best I can
| tell it's just your project (that is, not a whole team of 50
| engineers who have to collaborate on the codebase) so you are
| the audience being done a disservice by continuing to battle
| a language that hates you
|
| As for the interop, yes, since the 70s any language that
| can't call into a C library is probably DoA but that list
| isn't the empty set, as you pointed out with the ones you've
| actually considered. I'd even _suspect_ if you tried Golang
| it may even bring SumatraPDF to other platforms which would
| be another huge benefit to your users
| mhd wrote:
| > I'd even suspect if you tried Golang it may even bring
| SumatraPDF to other platforms which would be another huge
| benefit to your users
|
| Probably by using a cross-platform toolkit written in C++.
| kjksf wrote:
| Don't worry about being nice, 15 years doing open source
| develops a thick skin.
|
| But you didn't respond: which language should I use?
|
| Go didn't exist when I started SumatraPDF.
|
| And while I write pretty much everything else in Go and
| love the productivity, it wouldn't be a good fit.
|
| A big reason people like Sumatra is that it's fast and
| small. 10 MB (of which majority are fonts embedded in the
| binary) and not 100MB+ of other apps.
|
| Go's "hello world" is 10 MB.
|
| Plus abysmal (on Windows) interop with C / C++ code.
|
| And the reason SumatraPDF is unportable to mac / linux is
| not the language but the fact that I use all the Windows
| API I can for the UI.
|
| Any cross-platform UI solution pretty much require using
| tens of megabytes of someone else's reimplementation of all
| the UI widgets (Qt, GTK, Flutter) or re-implementing a
| smaller subset of UI using less code.
| sitzkrieg wrote:
| sumatrapdf not being cross platform is a great feature,
| maximizing the use of intended platform. win32api is
| great. thank you for that
| nashashmi wrote:
| Yes, I now seem to enjoy win32api as it is very snappy,
| compared to the hell that other cross platform solutions
| are introducing.
| nashashmi wrote:
| I would have recommended Go but if it increases file
| size, please don't. Anyways, it is very fast and quick so
| keep it the way it is. I can't find a reason to change
| it.
|
| It can even do comments. But I would like to see more
| comment tools, especially measurement tools.
|
| And since you are using Windows, do you think it would be
| worthwhile to add Windows OCR?
| CyberDildonics wrote:
| _being done a disservice by continuing to battle a language
| that hates you_
|
| I think you should learn basic modern C++ before making
| judgements like this.
|
| While you are upset about it the rest of the world is just
| using basic data structures, looping through them, never
| dealing with garbage collection and enjoying fast and light
| software that can run on any hardware.
|
| A lot of people could get by with $100 computers if all
| software was written like sumatraPDF.
| cherryteastain wrote:
| Why not just pass around an
| std::pair<void(*)(FuncData*), std;:unique_ptr<FuncData>>
|
| at this stage? This implementation has a bunch of performance and
| ergonomics issues due to things like not using perfect forwarding
| for the Func1::Call(T) method, so for anything requiring copying
| or allocating it'll be a decent bit slower and you'll also be
| unable to pass anything that's noncopyable like an
| std::unique_ptr.
| kjksf wrote:
| I don't know fancy C++ so I don't understand your point about
| perfect forwarding.
|
| But I do know the code I write and you're wrong about
| performance of Func0 and Func1. Those are 2 machine words and
| all it takes to construct them or copy them is to set those 2
| fields.
|
| There's just no way to make it faster than that, both at
| runtime or at compile time.
|
| The whole point of this implementation was giving up fancy
| features of std::function in exchange for code that is small,
| fast (both runtime and at compilation time) and one that I 100%
| understand in a way I'll never understand std::function.
| cherryteastain wrote:
| In this function void Call(T arg) const {
| if (fn) { fn(userData, arg); }
| }
|
| Say you pass something like an std::vector<double> of size 1
| million into Call. It'll first copy the std::vector<double>
| at the point you invoke Call, even if you never call fn.
| Then, if fn is not nullptr, you'll then copy the same vector
| once more to invoke fn. If you change Call instead to
| void Call(T&& arg) const { if (fn) {
| fn(userData, std::forward<T>(arg)); } }
|
| the copy will not happen at the point Call is invoked.
| Additionally, if arg is an rvalue, fn will be called by
| moving instead of copying. Makes a big difference for
| something like std::vector<double> foo();
| void bar(Func1<std::vector<double>> f) { auto v =
| foo(); f(std::move(v)); }
| OskarS wrote:
| > But I do know the code I write and you're wrong about
| performance of Func0 and Func1. Those are 2 machine words and
| all it takes to construct them or copy them is to set those 2
| fields.
|
| You also have to heap allocate your userData, which is
| something std::function<> avoids (in all standard
| implementations) if it's small enough (this is why the
| sizeof() of std::function is larger than 16 bytes, so that it
| can optionally store the data inline, similar to the small
| string optimization). The cost of that heap allocation is not
| insignificant.
|
| If I were doing this, I might just go the full C route and
| just use function pointers and an extra "userData" argument.
| This seems like an awkward "middle ground" between C and C++.
| badmintonbaseba wrote:
| Just use std::function, you don't have to pass a lambda. Any
| callable is fine.
| _randyr wrote:
| I'm not a C++ programmer, but I was under the impression that
| closures in c++ were just classes that overload the function call
| operator `operator()`. So each closure could also be implemented
| as a named class. Something like: class
| OnListItemSelected { OnListItemSelectedData data;
| void operator()(int selectedIndex) { ... } }
|
| Perhaps I'm mistaken in what the author is trying to accomplish
| though?
| OskarS wrote:
| Indeed, that is exactly the case, lambdas are essentially
| syntax sugar for doing this.
|
| The one thing the author's solution does which this solution
| (and lambdas) does not is type erasure: if you want to pass
| that closure around, you have to use templates, and you can't
| store different lambdas in the same data structure even if they
| have the same signature.
|
| You could solve that in your case by making `void operator()`
| virtual and inheriting (though that means you have to heap-
| allocate all your lambdas), or use `std::function<>`, which is
| a generic solution to this problem (which may or may not
| allocate, if the lambda is small enough, it's usually optimized
| to be stored inline).
|
| I get where the author is coming from, but this seems very much
| like an inferior solution to just using `std::function<>`.
| usefulcat wrote:
| > though that means you have to heap-allocate all your
| lambdas
|
| I think whether or not you have to allocate from the heap
| depends on the lifetime of the lambda. Virtual methods also
| work just fine on stack-allocated objects.
| OskarS wrote:
| Fair point, but generally speaking, callbacks tend to
| escape the scopes they are in (if you have a callback for
| "user clicked mouse", it's likely not going to be triggered
| in your current scope), so stack-allocation isn't really an
| option.
|
| But yes, fair point: they can be stack or statically
| allocated as well.
| pwagland wrote:
| The author of the article freely admits that
| `std::function<>` is more flexible. He still prefers this
| solution, as it is easier for him to reason about. This is
| covered in the "Fringe Benefits" part of the document.
| spacechild1 wrote:
| Exactly! And if you need type erasure, you can just store it in
| a std::function.
|
| > OnListItemSelectedData data;
|
| In this case you can just store the data as member variables.
| No need for defining an extra class just for the data.
|
| As I've written elsewhere, you can also just use a lambda and
| forward the captures and arguments to a (member) function. Or
| if you're old-school, use std::bind.
| InfiniteRand wrote:
| Main issue author had with lambdas is autogenerated names in
| crash reports
| _randyr wrote:
| Yes, but that's exactly why I mention this. By explicitly
| creating a class (that behaves the same as a lambda) the
| author might get better names in crash reports.
| noomen wrote:
| I just want to thank SumatraPDF's creator, he literally saved my
| sanity from the evil that Adobe Acrobat Reader is. He probably
| saved millions of people thousands of hours of frustration using
| Acrobat Reader.
| pjmlp wrote:
| Another example of NIH, better served by using the standard
| library.
| kjksf wrote:
| Programming requires making fine-grained implementation
| decisions.
|
| There are numerous differences between my Func0 and Func1<T>
| and std::function<>.
|
| Runtime size, runtime performance, compilation speed,
| understandability of the code, size of the source code, size of
| the generated code, ergonomics of use.
|
| My solution wins on everything except ergonomics of use.
|
| LLVM has a small vector class.
|
| When asked for comment, pjmlp said: "Another example of NIH,
| better served by using the standard library".
| pjmlp wrote:
| First of all there is the whole how much performance gain
| that actually wins in practice, when everyone amd their dog
| are shipping Electron apps.
|
| Secondly, the maintainability of duplicating standard library
| code, without having the same resources as the compiler
| vendor.
|
| It is your product, naturally you don't have to listen to
| folks like myself.
| AlexandrB wrote:
| I think context is important. In a large corporate context,
| NIH kills you because everything you implement needs to be
| documented, debugged, and understood by 10s or 100s of
| other people. In a small or one-man project, a lot of the
| NIH downsides go away and it makes (some) sense to reinvent
| the wheel if there are performance or simplicity benefits
| to be had. Consider Roller Coaster Tycoon as an example of
| the latter - where the author wrote everything in asm out
| of personal preference and for performance reason instead
| of using C and its libraries.
|
| I'm surprised by how many people on HN are yelling at the
| author to code as if he's working at a company like Adobe,
| when objectively Adobe's PDF reader is dogshit (especially
| performance wise) for most people and is probably built on
| best practices like using standard libraries.
| spacechild1 wrote:
| > I've used std::function<> and I've used lambdas and what pushed
| me away from them were crash reports.
|
| In danger of pointing out the obvious: std::function does note
| require lambdas. In fact, it has existed long before lambdas
| where introduced. If you want to avoid lambdas, just use
| std::bind to bind arguments to regular member functions or free
| functions. Or pass a lambda that just forwards the captures and
| arguments to the actual (member) function. There is no reason for
| regressing to C-style callback functions with user data.
| mandarax8 wrote:
| std::bind is bad for him for the same reasons std::function is
| bad though
| spacechild1 wrote:
| Why? If the bound (member) function crashes, you should get a
| perfectly useable crash report. AFAIU his problem was that
| lambdas are anonymous function objects. This is not the case
| here, because the actual code resides in a regular (member)
| function.
| dustbunny wrote:
| Does a stack trace from a crash in a bound function show
| the line number of where the bind() took place?
| delusional wrote:
| Assuming the stack trace is generated by walking up the
| stack at the time when the crash happened, nothing that
| works like a C function pointer would ever do that.
| Assigning a a pointer to a memory location doesn't
| generate a stack frame, so there's no residual left in
| the stack that could be walked back.
|
| A simple example. If you were to bind a function pointer
| in one stack frame, and the immediately return it to the
| parent stack frame which then invokes that bound pointer,
| the stack that bound the now called function would
| literally not exist anymore.
| spacechild1 wrote:
| No, but neither does the author's solution.
| kjksf wrote:
| I did use bind earlier in SumatraPDF.
|
| There are 2 aspects to this: programmer ergonomics and other
| (size of code, speed of code, compilation speed,
| understandability).
|
| Lambdas with variable capture converted to std::function have
| best ergonomics but at the cost of unnamed, compiler-generated
| functions that make crash reports hard to read.
|
| My Func0 and Func1<T> approach has similar ergonomics to
| std::bind. Neither has the problem of potentially crashing in
| unnamed function but Func0/Func1<T> are better at other
| (smaller code, faster code, faster compilation).
|
| It's about tradeoffs. I loved the ergonomics of callbacks in C#
| but I working within limitations of C++ I'm trying to find
| solutions with attributes important to me.
| spacechild1 wrote:
| > but Func0/Func1<T> are better at other (smaller code,
| faster code, faster compilation).
|
| I would really question your assumptions about code size,
| memory usage and runtime performance. See my other comments.
| Kranar wrote:
| > In fact, it has existed long before lambdas where introduced.
|
| Both std::function<> and lambdas were introduced in C++11.
|
| Furthermore absolutely no one should use std::bind, it's an
| absolute abomination.
| spacechild1 wrote:
| You are absolutely right of course! No idea, why I thought
| std::function existed before C++11. Mea culpa!
|
| > Furthermore absolutely no one should use std::bind, it's an
| absolute abomination.
|
| Agree 100%! I almost always use a wrapper lambda.
|
| However, it's worth pointing out that C++20 gave us
| std::bind_front(), which is really useful if you want to just
| bind the first N arguments: struct Foo {
| void bar(int a, int b, int c); }; Foo
| foo; using Callback = std::function<void(int,
| int, int)>; // with std::bind (ugh):
| using namespace std::placeholders; Callback
| cb1(std::bind(&Foo::bar, &foo, _1, _2, _3)); //
| lambda (without perfect forwarding): Callback
| cb2([&foo](auto&&... args) { foo.bar(args...); });
| // lambda (with perfect forwarding): Callback
| cb3([&foo](auto&&... args) {
| foo.bar(std::forward<decltype(args)>(args)...); });
| // std::bind_front Callback
| cb4(std::bind_front(&Foo::bar, &foo));
|
| I think std::bind_front() is the clear winner here.
| mandarax8 wrote:
| What he shows here is 75% of c++26's std::function_ref. It's
| mainly missing variadic arguments and doesn't support all types
| of function objects.
|
| https://github.com/TartanLlama/function_ref/blob/master/incl...
| kjksf wrote:
| I can honestly say that I couldn't write that thing in 100
| years.
|
| I can't even read it.
|
| That's the fundamental problem with C++: I've understood pretty
| much all Go code I ever looked at.
|
| The code like the above is so obtuse that 0.001% of C++
| programmers is capable of writing it and 0.01% is capable of
| understanding it.
|
| Sure, I can treat it as magic but I would rather not.
| spacechild1 wrote:
| Do you understand how your compiler works? Shouldn't you be
| writing assembly instead? You can't understand all internals
| and that's perfectly fine.
|
| Why do you even care how std::function is implemented?
| (Unless you are working in very performance critical or
| otherwise restricted environments.)
| kjksf wrote:
| I've listed several reasons why I decided to write and use
| this implementation: - better call stacks
| in crash reports - smaller and faster at runtime
| - faster compilation because less complicated, less
| templated code - I understand it
|
| So there's more to it that just that one point.
|
| Did I loose useful attributes? Yes. There's no free lunch.
|
| Am I going too far to achieve small, fast code that
| compiles quickly? Maybe I do.
|
| My code, my rules, my joy.
|
| But philosophically, if you ever wonder why most software
| today can't start up instantly and ships 100 MB of stuff to
| show a window: it's because most programmers don't put any
| thought or effort into keeping things small and fast.
| spacechild1 wrote:
| Oh, I definitely agree with some of your other points,
| just not the one I argued against.
|
| BTW, I would also contest that your version is faster at
| runtime. Your data always allocated on the heap.
| Depending on the size of the data, std::function can
| utilize small function optimization and store everything
| in place. This means there is no allocation when setting
| the callback and also better cache locality when calling
| it. Don't make performance claims without benchmarking!
|
| Similarly, the smaller memory footprint is not as clear
| cut: with small function optimization there might be
| hardly a difference. In some cases, std::function might
| even be smaller. (Don't forget about memory allocation
| overhead!)
|
| The only point I will absolutely give you is compilation
| times. But even there I'm not sure if std::function is
| your bottleneck. Have you actually measured?
| kjksf wrote:
| That's a fair point. I just looked and out of 35 uses of
| MkFunc0 only about 3 (related to running a thread)
| allocate the args.
|
| All others use a pointer to an object that exists anyway.
| For example, I have a class MyWindow with a button. A
| click callback would have MyWindow* as an argument
| because that's the data needed to perform that action.
| That's the case for all UI widgets and they are majority
| uses of callbacks.
|
| I could try to get cheeky and implement similar
| optimization as Func0Fat where I would have inline buffer
| on N bytes and use it as a backing storage for the
| struct. But see above for why it's not needed.
|
| As to benchmarking: while I don't disagree that
| benchmarking is useful, it's not the ace card argument
| you think it is.
|
| I didn't do any benchmarks and I do no plan to.
|
| Because benchmarking takes time, which I could use
| writing features.
|
| And because I know things.
|
| I know things because I've been programming, learning,
| benchmarking for 30 years.
|
| I know that using 16 bytes instead of 64 bytes is faster.
| And I know that likely it won't be captured by a
| microbenchmark.
|
| And even if it was, the difference would be miniscule.
|
| So you would say "pfft, I told you it was not worth it
| for a few nanoseconds".
|
| But I know that if I do many optimizations like that,
| it'll add up even if each individual optimization seems
| not worth it.
|
| And that's why SumatraPDF can do PDF, ePub, mobi, cbz/cbr
| and uses less resources that Windows' start menu.
| spacechild1 wrote:
| First, thanks for providing SumataraPDF as free software!
| I don't want to disparage your software in any way. I
| don't really care how it's written as long as it works
| well - and it does! This is really just about your blog
| post.
|
| > I just looked and out of 35 uses of MkFunc0 only about
| 3 (related to running a thread) allocate the args.
|
| In that case, std::function wouldn't allocate either.
|
| > All others use a pointer to an object that exists
| anyway. For example, I have a class MyWindow with a
| button. A click callback would have MyWindow* as an
| argument because that's the data needed to perform that
| action. That's the case for all UI widgets and they are
| majority uses of callbacks.
|
| That's what I would have guessed. Either way, I would
| just use std::bind or a little lambda:
| struct MyWindow { void onButtonClicked(); };
| // old-school: std::bind
| setCallback(std::bind(&MyWindow::onButtonClicked,
| window)); // modern: a simple lambda
| setCallback([window]() { window->onButtonClicked(); });
|
| If your app crashes in MyWindow::onButtonClicked, that
| method would be on the top of the stack trace. IIUC this
| was your original concern. Most of your other points are
| just speculation. (The compile time argument technically
| holds, but I'm not sure to which extend it really shows
| in practice. Again, I would need some numbers.)
|
| > I know things because I've been programming, learning,
| benchmarking for 30 years.
|
| Thinking that one "knows things" is dangerous. Things
| change and what we once learned might have become
| outdated or even wrong.
|
| > I know that using 16 bytes instead of 64 bytes is
| faster. And I know that likely it won't be captured by a
| microbenchmark.
|
| Well, not necessarily. If you don't allocate any capture
| data, then your solution will win. Otherwise it might
| actually perform worse. In your blog post, you just
| claimed that your solution is faster overall, without
| providing any evidence.
|
| Side note: I'm a bit surprised that std::function takes
| up 64 bytes in 64-bit MSVC, but I can confirm that it's
| true! With 64-bit GCC and Clang it's 32 bytes, which I
| find more reasonable.
|
| > And even if it was, the difference would be miniscule.
|
| That's what I would think as well. Personally, I wouldn't
| even bother with the performance of a callback function
| wrapper in a UI application. It just won't make a
| difference.
|
| > But I know that if I do many optimizations like that,
| it'll add up even if each individual optimization seems
| not worth it.
|
| Amdahl's law still holds. You need to optimize the parts
| that actually matter. It doesn't mean you should be
| careless, but we need to keep things in perspective. (I
| _would_ care if this was called hundreds or thousands of
| times within a few milliseconds, like in a realtime audio
| application, but this is not the case here.)
|
| To be fair, in your blog post you do concede that
| std::function has overall better ergonomics, but I still
| think you are vastly overselling the upsides of your
| solution.
| maleldil wrote:
| > You can't understand all internals, and that's perfectly
| fine.
|
| C++ takes this to another level, though. I'm not an expert
| Go or Rust programmer, but it's much easier to understand
| the code in their standard libraries than C++.
| spacechild1 wrote:
| Fair enough :) Unfortunately, this is just something one
| has to accept as a C++ programmer. Should we roll our own
| std::vector because we can't understand the standard
| library implemention? The answer is, of course, a firm
| "no" (unless you have very special requirements).
| mandarax8 wrote:
| Yeah it's a shame that to go from your idea to something
| that's 'general' (ie just some arbitrary arguments) you need
| to write this arcane garbage.
| winocm wrote:
| Consider this implementation:
|
| https://llvm.org/doxygen/STLFunctionalExtras_8h_source.html
| moonshadow565 wrote:
| It's not that complex if you remove all the stuff you don't
| use: https://godbolt.org/z/rM9ejojv4 .
|
| Main things you would need to understand is specialization
| (think like pattern matching but compile time) and pack
| expansion (three dots).
| commandersaki wrote:
| I always love this author's writing style, his articles are pure
| bliss to read.
| mwkaufma wrote:
| The lengths some go to avoid just using a bog-standard virtual
| function.
| kjksf wrote:
| I actually used the "virtual function" approach earlier in
| SumatraPDF.
|
| The problem with that is that for every type of callback you
| need to create a base class and then create a derived function
| for every unique use.
|
| That's a lot of classes to write.
|
| Consider this (from memory so please ignore syntax errors, if
| any): class ThreadBase { virtual
| void Run(); // ... } class
| MyThread : ThreadBase { MyData* myData;
| void Run() override; // ... }
| StartThread(new MyThread());
|
| compared to: HANDLE StartThread(const Func0&,
| const char* threadName = nullptr); auto fn =
| MkFunc0(InstallerThread, &gCliNew); StartThread(fn,
| "InstallerThread");
|
| I would have to create a base class for every unique type of
| the callback and then for every caller possibly a new class
| deriving.
|
| This is replaced by Func0 or Func1<T>. No new classes, much
| less typing. And less typing is better programming ergonomics.
|
| std::function arguably has slightly better ergonomics but
| higher cost on 3 dimension (runtime, compilation time,
| understandability).
|
| In retrospect Func0 and Func1 seem trivial but it took me years
| of trying other approaches to arrive at insight needed to
| create them.
| mwkaufma wrote:
| >> I would have to create a base class for every unique type
| of the callback and then for every caller possibly a new
| class deriving.
|
| An interface declaration is, like, two lines. And a single
| receiver can implement multiple interfaces. In exchange, the
| debugger gets a lot more useful. Plus it ensures the lifetime
| of the "callback" and the "context" are tightly-coupled, so
| you don't have to worry about intersecting use-after-frees.
| gpderetta wrote:
| You could do: template<class R, class...
| Args> struct FnBase { virtual R
| operator()(Args...) = 0; }; class
| MyThread : FnBase<void> { ... };
| mwkaufma wrote:
| This is pithy and clever, but in exchange makes the code
| less obviously self-documenting (implementation details
| creeping into type declarations) and complicates
| implementing multiple interfaces on the same receiver.
| mgaunard wrote:
| Should have just implemented his own std::function with the
| simplicity and performance trade-off he wanted.
| akdev1l wrote:
| I don't really understand what problem this is trying to solve
| and how the solution is better than std::function. (I understand
| the issue with the crash reports and lambdas being anonymous
| classes but not sure how the solution improved on this or how
| std::function has this problem?)
|
| I haven't used windows in a long time but back in the day I
| remember installing SumatraPDF to my Pentium 3 system running
| windows XP and that shit rocked
| kjksf wrote:
| How is Func0 / Func1<T> better than std::function?
|
| Smaller size at runtime (uses less memory).
|
| Smaller generated code.
|
| Faster at runtime.
|
| Faster compilation times.
|
| Smaller implementation.
|
| Implementation that you can understand.
|
| How is it worse?
|
| std::function + lambda with variable capture has better
| ergonomics i.e. less typing.
| akdev1l wrote:
| I think none of these points are demonstrated in the post
| hence I fail to visualize it
|
| Also I copy pasted the code from the post and I got this:
|
| test.cpp:70:14: error: assigning to 'void _' from 'func0Ptr'
| (aka 'void (_)(void *)') converts between void pointer and
| function pointer 70 | res.fn = (func0Ptr)fn;
| cryptonector wrote:
| > test.cpp:70:14: error: assigning to 'void ' from
| 'func0Ptr' (aka 'void ()(void *)') converts between void
| pointer and function pointer 70 | res.fn = (func0Ptr)fn;
|
| This warning is stupid. It's part of the "we reserve the
| right to change the size of function pointers some day so
| that we can haz closures, so you can't assume that function
| pointers and data pointers are the same size m'kay?"
| silliness. And it is silly: because the C and C++
| committees will never be able to change the size of
| function pointers, not backwards-compatibly. It's not that
| I don't wish they could. It's that they can't.
| akdev1l wrote:
| It's not a warning, it's a compile time error and I am
| not even using -Wall -Werror
|
| I also believe there are platforms where a function
| pointer and a data pointer are not the same but idk about
| such esoteric platforms first hand (seems Itanium had
| that: https://stackoverflow.com/questions/36645660/why-
| cant-i-cast...)
|
| Though my point was only that this code will not compile
| as is with whatever clang Apple ships*
|
| I am not really sure how to get it to compile tbqh
|
| Some further research ( https://www.kdab.com/how-to-cast-
| a-function-pointer-to-a-voi...) suggest it should be done
| like so:
|
| > auto fptr = &f; void _a = reinterpret_cast <void
| _&>(fptr);
|
| edit: I tried with GCC 15 and that compiled successfully
| comex wrote:
| It should just be res.fn = (void *)fn;
|
| `res.fn` is of type `void *`, so that's what the code
| should be casting to. Casting to `func0Ptr` there seems
| to just be a mistake. Some compilers may allow the
| resulting function pointer to then implicitly convert to
| `void *`, but it's not valid in standard C++, hence the
| error.
|
| Separately from that, if you enable -Wpedantic, you can
| get a warning for conversions between function and data
| pointers even if they do use an explicit cast, but that's
| not the default.
| gpderetta wrote:
| FWIW, POSIX practically requires void otr and function
| otr inter-convertibility hence the support from GCC.
| wahern wrote:
| Not practically, but literally: Note that
| conversion from a void * pointer to a function
| pointer as in: fptr = (int
| (*)(int))dlsym(handle, "my_function"); is not
| defined by the ISO C standard. This standard
| requires this conversion to work correctly on conforming
| implementations.
| gpderetta wrote:
| I remembered the dlsym requirement, I wasn't sure it was
| de-jure.
| kjksf wrote:
| Thanks, fixed.
|
| It works in msvc but as someone pointed out, it was a typo
| and was meant to be (void*) cast.
| almostgotcaught wrote:
| Your Func thing is better than std::function the same way a
| hammer is better than a drill press... ie it's not better
| because it's not the same thing at all. Yes the hammer can do
| _some_ of the same things, at a lower complexity, but it can
| 't do _all_ the same things.
|
| What I'm trying to say is being better than x means you can
| do all the same things as x _better_. Your thing is not
| better, it is just different.
| spacechild1 wrote:
| You can't just keep claiming these things without providing
| evidence. _How_ much faster? _How_ much smaller? These claims
| are meaningless without numbers to back it up.
| oezi wrote:
| I think the one key downside for std::function+lambda which
| resonated with me was bad ergonomics during debugging.
|
| My unanswered question on this from 8 years ago:
|
| https://stackoverflow.com/questions/41385439/named-c-
| lambdas...
|
| If there was a way to name lambdas for debug purposes then
| all other downsides would be irrelevant (for most usual use
| cases of using callbacks).
| neobrain wrote:
| > If there was a way to name lambdas for debug purposes
| then all other downsides would be irrelevant (for most
| usual use cases of using callbacks).
|
| Instead of fully avoiding lambdas, you can use inheritance
| to give them a name: https://godbolt.org/z/YTMo6ed8T
|
| Sadly that'll only work for captureless lambdas, however.
| m-schuetz wrote:
| None of the arguments on this list seem convincing. The only
| one that makes sense was the argument that it helps identify
| the source of a crash.
|
| How much smaller is it? Does it reduce the binary size and
| RAM usage by just 100 bytes?
|
| Is it actually faster?
|
| How much faster does it compile? 2ms faster?
| kjksf wrote:
| I didn't write that article to convince anybody.
|
| I wrote it to share my implementation and my experience
| with it.
|
| SumatraPDF compiles fast (relative to other C++ software)
| and is smaller, faster and uses less resources that other
| software.
|
| Is it because I wrote Func0 and Func1 to replace
| std::function? No.
|
| Is it because I made hundreds decisions like that? Yes.
|
| You're not wrong that performance wins are miniscule.
|
| What you don't understand is that eternal vigilance is the
| price of liberty. And small, fast software.
| pwagland wrote:
| This is a valid point missed by many today. The mantra of
| don't optimise early is often used as an excuse to not
| optimise at all, and so you end up with a lot of minor
| choices scattered throughout the code with all suck a
| tiny bit of performance out of the system. Fixing any of
| these is also considered to be worthless, as the
| improvement from any one change is miniscule. But added
| up, they become noticeable.
| SuperV1234 wrote:
| > Is it because I made hundreds decisions like that? Yes.
|
| Proof needed. Perhaps your overall program is designed to
| be fast and avoid silly bottlenecks, and these "hundred
| decisions" didn't really matter at all.
| fsloth wrote:
| In my experience performance comes from constant
| vigilance and using every opportunity to choose the
| performant way of implementing something.
|
| Silly bottlenecks are half of the perf story in my
| experience. The other half are a billion tiny details.
| badmintonbaseba wrote:
| > Smaller size at runtime (uses less memory).
|
| Yours is smaller (in terms of sizeof), because std::function
| employs small-buffer optimization (SBO). That is if the user
| data fits into a specific size, then it's stored inline the
| std::function, instead of getting heap allocated. Yours need
| heap allocation for the ones that take data.
|
| Whether yours win or lose on using less memory heavily
| depends on your typical closure sizes.
|
| > Faster at runtime
|
| Benchmark, please.
| benreesman wrote:
| It's a daily thing we all do: decide if this problem is better
| solved by a big chunk of code that is probably well tested but
| probably satisfies a bunch of requirements and other
| constraints or a smaller chunk of code that I can write or
| vendor in and has other advantages or maybe I just prefer how
| its spelled. Sometimes there's a "right" answer, e.g. you
| should generally link in your TLS implantation unless you're a
| professional TLS pereon, but usually its a judgement call, and
| the aggregate of all those micro-decisions are a component of
| the intangible ideal of "good taste" (also somewhat subjective
| but most agree on the concept of an ideal).
|
| In this instance the maintainer of a useful piece of software
| has made a choice that's a little less common in C++ (totally
| standard practice in C) and it seems fine, its on the bubble, I
| probably default the other way, but std::function is complex
| and there are platforms where that kind of machine economy is a
| real consideration, so why not?
|
| In a zillion contributor project I'd be a little more skeptical
| of the call, but even on massive projects like the Linux kernel
| they make decisions about the house style that seem unorthodox
| to outsiders and they have their reasons for doing so. I
| misplaced the link but a kernel maintainer raised grep-
| friendliness as a reason he didn't want a patch. At first I was
| like, nah you're not saying the real reason, but I looked a
| little and indeed, the new stuff would be harder to navigate
| without a super well-configured LSP.
|
| Longtime maintainers have reasons they do things a certain way,
| and the real test is the result. In this instance (and in most)
| I think the maintainer seems to know what's best for their
| project.
| akdev1l wrote:
| I guess the point is that the articule does not prove what he
| did is better in any of the ways he claimed except for the "I
| understand it" part
|
| Making changes like this claiming it will result in faster
| code or more smaller code without any test or comparison
| before vs after seems to be not the best way of engineering
| something
|
| I think this is why the thread has seen a lot of push back
| overall
|
| Maybe the claims are true or maybe they are not - we cannot
| really say based on the article (though I'm guessing not
| really)
| benreesman wrote:
| Yeah, it seems unlikely that the typical target machine
| would have either a word or cache line size that spilled a
| std::function via overhead on a realistic closure, but who
| knows, I would bet real money either way without a profile.
|
| And I think it is less than ideal as concerns the fragile
| abd nascent revival of mainstream C++ to have this sort of
| a gang tackle over a nitpick like this. The approach is
| clearly fine because its how most every C program works.
|
| The memes of C++ as too hard for the typical programmer and
| C++ programmers as pedantic know-it-all types are mostly
| undeserved, but threads like this I think reinforce those
| negative stereotypes.
|
| The real S-Tier C++ people who are leading the charge on
| getting C++ back in the mindshare game (~ Herb Sutter's
| crew) are actively fighting both memes and I think it
| behooves all of us who want the ecosystem to thrive should
| follow their lead.
|
| The danger of C++ becoming unimportant in the next five or
| ten years is zero, C and C++ are what the world runs on in
| important ways.
|
| But in 20? 30? The top people are working with an urgency I
| haven't seen in decades and the work speaks for itself: 23
| and 26 are coming together "chef's kiss" as Opus would say.
|
| The world is a richer place with Rust and Zig in it, but it
| would be a poorer place with C++ gone, and that's been the
| long term trend until very recently.
| vanderZwan wrote:
| I'm surprised that many comments here seem to have missed this
| bit of context:
|
| > _One thing you need to know about me is that despite working on
| SumatraPDF C++ code base for 16 years, I don't know 80% of C++._
|
| I'm pretty sure that most "why don't you just use x..." questions
| are implicitly answered by it, with the answer being "because
| using x correctly requires learning about all of it's intricacies
| and edge-cases, which in turn requires understanding related
| features q, r, s... all the way to z, because C++ edge-case
| complexity doesn't exist in a vacuum".
| throwaway2037 wrote:
| I agree: This quote is the star of the show. I'll troll a
| little bit here: I thought to myself: "Finally, a humble C++
| programmer. Really, they do exist... well, at least one."
| uncircle wrote:
| There's two:
|
| > Even I can't answer every question about C++ without
| reference to supporting material (e.g. my own books, online
| documentation, or the standard). I'm sure that if I tried to
| keep all of that information in my head, I'd become a worse
| programmer.
|
| -- Bjarne Stroustrup, creator of C++
| vanderZwan wrote:
| Bjarne has his moments - I like his saying that somewhere
| buried underneath all of C++'s complexity there's an
| elegant language struggling to get out, and I'm sympathetic
| to his frustrations and believe he does have good
| intentions there.
|
| But he can also contradict himself sometimes in this
| regard, because he also often uses a variation of calling
| C++ a language for _" people who know what they are doing"_
| as a sort of catch-all dismissal of critiques of its
| footguns.
|
| The whole problem is that very few people can claim to
| truly "know what they are doing" when it comes to all of
| C++' features and how they interconnect, dismissing that by
| (implicitly) telling people to just "git gud" is missing
| the point a bit.
|
| But again, he's only human and I do get the urge to get a
| bit defensive of your baby.
| nxobject wrote:
| There's another slogan that also acts as catch-all
| dismissal - "easy things should be easy; hard things
| should be possible". Yes, but the bar for "hard things"
| just happens to be frustratingly low compared to other
| languages - ie library programming that has enough
| genericity and robustness. To wit, this example.
| throwaway2037 wrote:
| > easy things should be easy; hard things should be
| possible
|
| From many years ago, this was a Perl motto from Larry
| Warry. Is the original pontificator... or was it someone
| before him?
| favorited wrote:
| He also seems completely uninterested in finding that,
| "elegant language struggling to get out." He just asserts
| that it's there, as if its mere existence is a virtue.
|
| I think Herb Sutter is at least trying to find that
| elegant language, with his "syntax v2" project. It's one
| way to preserve compatibility with the incalculable
| amount of C++ in the wild, while also providing a
| simplified syntax with better defaults and fewer foot-
| guns.
|
| Of course, Herb isn't immune to making hand-wavy
| claims[0] of his own, but he seems to bring forward more
| good ideas than bad.
|
| [0] https://herbsutter.com/2025/03/30/crate-training-
| tiamat-un-c...
| jahnu wrote:
| 30 year c++ veteran here. Also don't know 80%. I used to know
| more but a combination of way better tooling and "modern" c++
| and most importantly realising that thinking more about things
| other than the language details led to better software meant I
| have forgotten stuff I used to know.
| comex wrote:
| Note that some CFI (control flow integrity) implementations will
| get upset if you call a function pointer with the wrong argument
| types:
|
| https://gcc.godbolt.org/z/EaPqKfvne
|
| You could get around this by using a wrapper function, at the
| cost of a slightly different interface:
| template <typename T, void (*fn)(T *)> void wrapper(void
| *d) { fn((T *)d); } template
| <typename T, void (*fn)(T *)> Func0 MkFunc0(T* d) {
| auto res = Func0{}; res.fn = (void *)wrapper<T, fn>;
| res.userData = (void*)d; return res; }
| ... Func0 x = MkFunc0<int, my_func>(nullptr);
|
| (This approach also requires explicitly writing the argument
| type. It's possible to remove the need for this, but not without
| the kind of complexity you're trying to avoid.)
| CraftingLinks wrote:
| "Templated code is a highway to bloat." Should be on a t-shirt.
| petters wrote:
| > In fact, I don't think anyone understands std::function<>
| including the 3 people who implemented it.
|
| "I don't understand it, so surely it must be very difficult and
| probably nobody understands it"
| fredrikholm wrote:
| Here's the GCC implementation of `std::function`:
|
| https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-...
|
| I'm with OP here.
| skrebbel wrote:
| This seems very similar to Java's oldschool single-interface
| callback mechanism. Originally, Java didn't have lambdas or
| closures or anything of the sort, so instead they'd litter the
| standard library with single-method interfaces with names like
| ActionListener, MouseListener, ListItemSelectedListener, etc.
| You'd make a class that implements that interface, manually
| adding whatever data you need in the callback (just like here),
| and implement the callback method itself of course.
|
| I think that has the same benefit as this, that the callbacks are
| all very clearly named and therefore easy to pick out of a stack
| trace.
|
| (In fact, it seems like a missed opportunity that modern Java
| lambdas, which are simply syntactical sugar around the same
| single-method interface, do not seem to use the interface name in
| the autogenerated class)
| spullara wrote:
| They don't autogenerate classes anymore, just private static
| methods though I agree that it would be nice to have more of
| the metadata in the name of the generated method.
| skrebbel wrote:
| Oh really? Cool, I did not know that.
|
| How does that work with variables in the closure then? I
| could see that work with the autogenerated class: Just make a
| class field for every variable referenced inside the lambda
| function body, and assign those in constructor. Pretty
| similar to this here article. But it's not immediately
| obvious to me how private static methods can be used to do
| the same, except for callbacks that do not form a closure (eg
| filter predicates and sort compare functions and the likes
| that only use the function parameters).
| spullara wrote:
| Ah there is some nuance. For capturing lambdas they do
| generate a class at runtime to capture the variables but it
| still then just calls the generated private method with the
| simplistic naming scheme. Also, apparently the simple
| naming scheme was chosen so as to not go down the C++
| mangled name path and just depend on the debugging
| information.
| agent327 wrote:
| It's unfortunate that the author hasn't spent some time figuring
| out how to get a stack trace, that would have saved him from
| reinventing std::function badly.
|
| Also, sad to see people still using new. C++11 was 14 years ago,
| for crying out loud...
| _ZeD_ wrote:
| > In programming language lingo, code + data combo is called a
| closure.
|
| in my day code + data was called a class :)
|
| (yeah, yeah, I know closure and class may be viewed as the same
| thing, and I know the Qc Na koan)
| vbezhenar wrote:
| Class is a set of functions. Closure is one function.
|
| In old Java, it really was a class. In new Java, I'm not 100%
| sure anymore, but with verbose syntax it'll be an class. I made
| it as verbose as possible: Function<Integer,
| Integer> adder(int x) { class Adder implements
| Function<Integer, Integer> { int x1;
| @Override Integer apply(Integer y) { return x1 + y;
| } } Adder adder = new Adder();
| adder.x1 = x; return adder; }
|
| and a bit less verbose with modern Java:
| Function<Integer, Integer> adder(int x) { return (y) ->
| x + y; }
|
| So closure could be definitely be considered as a very simple
| class.
| vanschelven wrote:
| For those not in the know:
|
| The venerable master Qc Na was walking with his student, Anton.
| Hoping to prompt the master into a discussion, Anton said
| "Master, I have heard that objects are a very good thing - is
| this true?" Qc Na looked pityingly at his student and replied,
| "Foolish pupil - objects are merely a poor man's closures."
|
| Chastised, Anton took his leave from his master and returned to
| his cell, intent on studying closures. He carefully read the
| entire "Lambda: The Ultimate..." series of papers and its
| cousins, and implemented a small Scheme interpreter with a
| closure-based object system. He learned much, and looked
| forward to informing his master of his progress.
|
| On his next walk with Qc Na, Anton attempted to impress his
| master by saying "Master, I have diligently studied the matter,
| and now understand that objects are truly a poor man's
| closures." Qc Na responded by hitting Anton with his stick,
| saying "When will you learn? Closures are a poor man's object."
| At that moment, Anton became enlightened.
|
| https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/...
| pif wrote:
| > I don't understand std::function<> implementation.
|
| This is the kind of (maybe brilliant, maybe great, maybe both,
| surely more than myself) developers I don't like to work with.
|
| You are not required to understand the implementation: you are
| only required to fully understand the contract. I hate those
| colleagues who waste my time during reviews because they need to
| delve deeply into properly-named functions before coming back to
| the subject at hand.
|
| Implementations are organized at different logical level for a
| reason. If you are not able to reason at a fixed level, I don't
| like to work with you (and I understand you will not like to work
| with me).
| skrebbel wrote:
| I'd be more sympathetic to your argument if this was about
| Python or Java web backends or something like that. But in C++,
| especially for a program like SumatraPDF with millions of
| installations on end-user computers where crashes can occur far
| away from a debugger, it's often borderline impossible to
| analyse problems without at least somewhat understanding the
| internals of every library feature you use.
|
| I think avoiding features you don't understand the
| implementation of makes a lot of sense in those kinds of
| situations.
|
| The hidden assumption in your comment is that the contract is
| implemented perfectly and that the abstraction isn't leaky.
| This isn't always the case. The author explained a concrete way
| in which the std::function abstraction leaks:
|
| > _They get non-descriptive, auto-generated names. When I look
| at call stack of a crash I can't map the auto-generated closure
| name to a function in my code. It makes it harder to read crash
| reports._
| spacechild1 wrote:
| > The author explained a concrete way in which the
| std::function abstraction leaks:
|
| But that's not an issue with std::function at all! His
| comment is really about lambdas and I don't understand why he
| conflates these two. Just don't put the actual code in a
| lambda: std::function<void()> cb([widget]()
| { widget->onButtonClicked(); });
|
| Here the code is in a method and the lambda is only used to
| bind an object to the method. If you are old-school, you can
| also do this with std::bind:
| std::function<void()> cb(std::bind(&Widget::onButtonClicked,
| widget));
| tlb wrote:
| I don't have this problem with backtraces in Clang. The
| 'anonymous' lambdas have debugging symbols named after the
| function it lexically appears in, something like
| parent_function::$_0::invoke. $_0 is the first lambda in that
| function, then $_1, etc. So it's easy enough to look up.
| lenkite wrote:
| This. I was confused when I read that - I guess MSVC doesn't
| generate such conventional lambda names ?
| badmintonbaseba wrote:
| It's up to the demangler, the info must be there in the
| decorated/mangled name. Demanglers sometimes choke on these
| complex symbols.
|
| AFAIK MSVC also changed their lambda ABI once, including
| mangling. As I recall at one point it even produced some hash
| in the decorated/mangled name, with no way to revert it, but
| that was before /Zc:lambda (enabled by default from C++20).
| bmn__ wrote:
| > [Lambdas] get non-descriptive, auto-generated names. When I
| look at call stack of a crash I can't map the auto-generated
| closure name to a function in my code.
|
| Well, HN lazyweb, how do you override the stupid name in C++? In
| other languages this is possible: $ node --trace-
| uncaught -e 'const c = function have_name() {throw null}; c()'
| $ perl -d:Confess -MSub::Util=set_subname -E 'my $c = sub()
| {die}; set_subname have_name => $c; $c->()'
| account42 wrote:
| Better solution would be to map to a source location (filename
| and line) instead.
| eddd-ddde wrote:
| Not necessary tho, the function already exists in the binary
| and already has a symbol. All you need is some
| compiler/language feature to change that symbol.
| detaro wrote:
| debug symbols already do that
| account42 wrote:
| "Surely no one will ever need to pass -1 as user data"
|
| Why place undefined behavior traps like that in your code.
| tempodox wrote:
| Slightly off-topic: Thanks to the author for SumatraPDF! It's an
| excellent Windows app that saves me (and many others, I'm sure)
| from having to use that horrible shit show that is Acrobat
| Reader.
| b0a04gl wrote:
| the approach works, but there's hidden cost in how it shapes the
| compiled output. every callback adds a layer the compiler has to
| guess around. curious if anyone checked what this does to
| inlining and branch prediction across builds. does the extra
| indirection prevent useful optimisations? or does the compiler
| end up being too aggressive and misoptimise when the struct
| layout changes later? would be useful to diff the assembly across
| releases and compilers
| AlienRobot wrote:
| >The implementation cleverness: use a special, impossible value
| of a pointer (-1) to indicate a function without arguments.
|
| From what I know about C this code probably breaks on platforms
| that nobody uses.
|
| Thanks for Sumatra, by the way :D Very useful software!
___________________________________________________________________
(page generated 2025-06-16 23:01 UTC)