12 ms·
Simplest C++ Callback, from SumatraPDF
- deleted 1y ago[deleted]
- deleted 1y ago[deleted]
- not-so-darkstar 1y agohttps://web.archive.org/web/20250615181542/https://blog.kowalczyk.info/a-stsj/simplest-c-callback-from-sumatrapdf.html https://web.archive.org/web/20250615181542/https://blog.kowa...
- kjksf 1y agoIt'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 1y agoA small kitten dies every time C++ is used like its 1995. void (*fn)(void*, T) = nullptr;
- plq 1y agoUnless you mutter the magic incantation "C compatibility" while doing it
- waynecochran 1y agoAt 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 1y agodid nullptr exist in c++ back in 1995 - i can't remember
- trealira 1y agoNope, 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 1y agoThat is the only thing "modern" in the above code and it does not save the kitten.
- tom_ 1y agoAnd 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 1y agoTo 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 1y agoDon't know about the code subtilities, but SumatraPDF is a gift for viewing PDF on MS Windows. So big thanks to the author !
- Arainach 1y agoOut 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 1y agoPart 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 1y agoThat's not always desirable though. I'd rather have control over that
- eviks 1y agoYou have, via a config. You have no control in a browser.
- jasonfarnon 1y agosame 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 1y agoIt'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.
- jitans 1y agoWhy don't use fu2? https://naios.github.io/function2/ https://naios.github.io/function2/
- kjksf 1y agoOne, 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 1y agoSumatraPDF 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 1y agoAs 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 1y agoI 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 1y ago> 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 1y agoDon'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.
- cherryteastain 1y agoWhy 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 1y agoI 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 1y agoIn 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 1y ago> 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++.
- _randyr 1y agoI'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 1y agoIndeed, 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 1y ago> 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 1y agoFair 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 1y ago
- noomen 1y agoI 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 1y agoAnother example of NIH, better served by using the standard library.
- kjksf 1y agoProgramming 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 1y agoFirst 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 1y agoI 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 1y ago> 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 1y agostd::bind is bad for him for the same reasons std::function is bad though
- spacechild1 1y agoWhy? 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 1y agoDoes a stack trace from a crash in a bound function show the line number of where the bind() took place?
- delusional 1y agoAssuming 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.
- mandarax8 1y agoWhat 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/include/tl/function_ref.hpp https://github.com/TartanLlama/function_ref/blob/master/incl...
- kjksf 1y agoI 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 1y agoDo 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 1y agoI'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 1y ago
- commandersaki 1y agoI always love this author's writing style, his articles are pure bliss to read.
- mwkaufma 1y agoThe lengths some go to avoid just using a bog-standard virtual function.
- kjksf 1y agoI 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 1y ago>> 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 1y agoYou could do: template<class R, class... Args> struct FnBase { virtual R operator()(Args...) = 0; }; class MyThread : FnBase<void> { ... };
- mgaunard 1y agoShould have just implemented his own std::function with the simplicity and performance trade-off he wanted.
- akdev1l 1y agoI 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 1y agoHow 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 1y agoI 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 1y ago> 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 1y ago
- vanderZwan 1y agoI'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 1y agoI 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 1y agoThere'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 1y agoBjarne 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.
- comex 1y agoNote 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 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 1y ago"Templated code is a highway to bloat." Should be on a t-shirt.
- petters 1y ago> 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 1y agoHere's the GCC implementation of `std::function`: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/std_function.h https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-... I'm with OP here.
- skrebbel 1y agoThis 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 1y agoThey 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 1y agoOh 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 1y agoAh 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 1y agoIt'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_ 1y ago> 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 1y agoClass 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 1y agoFor 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/msg03277.html https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/...
- pif 1y ago> 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 1y agoI'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 1y ago> 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 1y agoI 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 1y agoThis. I was confused when I read that - I guess MSVC doesn't generate such conventional lambda names ?
- badmintonbaseba 1y agoIt'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).
- psyclobe 1y agoOh that’s nice, wish gcc did that
- bmn__ 1y ago> [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 1y agoBetter solution would be to map to a source location (filename and line) instead.
- account42 1y ago"Surely no one will ever need to pass -1 as user data" Why place undefined behavior traps like that in your code.
- tempodox 1y agoSlightly 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 1y ago[dead]
- AlienRobot 1y ago>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!
- MaxBarraclough 1y ago> From what I know about C this code probably breaks on platforms that nobody uses. Your intuition is correct. Even if never dereferenced, casting a compile-time constant other than 0 to a pointer type in that way is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation. [0] If I understand correctly, saving a trap representation to a variable causes undefined behaviour, as doing so presumably constitutes reading the trap value [1] (I had a tough time finding a definitive answer on that point). I think it would be compliant to use the intptr_t type (assuming it exists on that platform) and give special treatment when it holds -1, i.e. never cast back to the pointer type if it holds -1. (Alternatively, use uintptr_t and give special treatment if it holds UINTPTR_MAX.) This would still rely on the assumption that the special value will never collide with the actual pointer values you need to work with, of course. [0] https://wiki.sei.cmu.edu/confluence/display/c/INT36-C.+Converting+a+pointer+to+integer+or+integer+to+pointer#INT36C.Convertingapointertointegerorintegertopointer-NoncompliantCodeExample.2 https://wiki.sei.cmu.edu/confluence/display/c/INT36-C.+Conve... [1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2091.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2091.htm