>There's no namespacing feature provided so you're left with the convention of picking a few letters as a prefix and hoping it doesn't overlap and yet is succinct enough to not be annoying.
I've been using C on a daily basis for 30+ years and name collisions has just never been a problem.
Granted, it might be due to lack of a package manager so micro dependencies ala import is_even is not a thing here, but still, in practice, no name collions occurs.
Never quite understood why people are so obsessed with meta programming capabilities in a language, be it templates, comptime, macros, whatever.
I program mostly in C, if I need 'meta' programming I just write another C program that processes C source code (I've written a simple C parser), then in my build script I build in two stages, build meta program, run it, build rest of program.
Simple, effective, debuggable (the meta program is just normal C), infinite capabilities - can nest this to arbitritary depths, need meta-meta programming? Make a program that generates a meta program.
One obvious answer is that people probably don’t want to write a whole parser and wire up new steps in their build pipeline just to do something simple like get the name of enum cases as a string.
Without taking a stance on whether in-language meta programming facilities are good or bad, it’s not hard to find examples of cases where people find it useful to have them.
Actually why even specify metaprogram as C like source code? It must be convenience. But there is little practical use, like a good program always models a lot of different representations of more or less the same things, just recombined and processed a little differently. Why would we want to deal with semantics of C types for example, if we can model a much clearer and better constrained universe of types used in e.g. a de/serialization framework? Even only pointers are quite special, and often only of very immediate use, but there is no point in e.g. persisting them to disk or sending them over the network.
Why would I write a parser that almost-but-not-quite matches the compiler's own parser, when I could just use the compiler's parser directly? I don't want to write a parser, and I especially don't want to debug weird corner cases where my implementation diverges somehow. I just want to write some code that goes like, for each field in T, do X.
C++ metaprogramming is bad, but the problem there is the C++ part, not the metaprogramming-in-the-language part.
Cause its dead simple. Shell out, run a quick sed or something, then compile it in. It is quite amazing what 'magic meta' stuff you can do with that shit. Meanwhile 10 years in we are finally getting reflection.....
Erm... that's not just false. The point of templates is generic programming, reusable components. If you don't put them in a header, you're not reusing them much. And if you have to "selectively pick TUs where they're instantiated", you're basically admitting that you have to invest effort to reduce compile times. You are refuting the very point you're making.
C++ templates _are_ slow to compile. They require running something like a dynamically typed VM in the compiler.
**** Template sets that took longest to instantiate:
833 ms: sf::base::Optional<$> (911 times, avg 0 ms)
Each individual instantiation of this class is sub 1ms.
Including the header itself takes 3ms.
I'm sure I can optimize it even further if I wanted to.
---
Now to refute your other incorrect claims:
> The point of templates is generic programming, reusable components.
That's ONE use case. A more general use case is just reducing code repetition in a type-safe manner, which is extremely useful even within the same translation unit. Another use case is metaprogramming. And I'm sure I can come up with more. Templates are a versatile tool.
> And if you have to "selectively pick TUs where they're instantiated", you're basically admitting that you have to invest effort to reduce compile times.
...well, yeah? Of course you have to put in effort to reduce compile times. That doesn't undermine my point at all.
Not slow to compile? 0,833 seconds extra compile time for a trivial utility class that doesn't do anything interesting other than make something perceived "safer"? Does that mean that each of the 911 instantiations took several million CPU ticks? You could convince me that it's not slow if it was 2-4 orders of magnitude less.
As I wrote elsewhere, 1 second is a timespan where we could aim to compile 1 MLOC of code on a single core.
> A more general use case is just reducing code repetition in a type-safe manner
As I said -- code reuse. And interestingly your Optional.hpp is a header...
That's a strange dismissal. `Optional<T>` isn't "perceived" safety -- it eliminates a whole category of bugs (null dereferences, uninitialized reads) at the type-system level, with zero runtime overhead versus a raw pointer or sentinel value.
If you think that's uninteresting, that's an aesthetic preference, not a technical argument.
But let's set that aside, because it's also irrelevant to the compile-time claim.
The point of the example wasn't "look at this fascinating class," it was "here is a real template, used 911 times across the codebase, in a public header -- exactly the scenario you said would be slow -- and it costs under 1ms per instantiation."
You can swap `Optional` for any non-trivial template of similar complexity and the numbers will look similar.
On your 1 MLOC/sec benchmark: that's a fair reference point for C-like code, but it's not the right yardstick for template instantiation, which is doing semantic work (overload resolution, SFINAE, constraint checking) that a C compiler simply isn't.
Comparing them is comparing different jobs.
The honest question is whether template compilation is slow relative to what it's actually doing, and in well-structured code, it isn't.
And yes, `Optional.hpp` is a header -- that's the whole point of the demonstration. I'm not claiming you should hide every template in a .cpp file. I'm claiming that even templates in headers, instantiated hundreds of times, are cheap when written with compile times in mind.
The "put templates in .cpp where it makes sense" advice is for the specific cases, not a blanket rule.
In practice, C means you end up with generic data structures with pointers to what they contain, rather than being inline.
You do see a lot of macro use to deal with this, but that is just primitive, non-typesafe metaprogramming, and it gets unwieldy enough that in practice, you see people add an extra pointer. This is why it gets slower.
In practice, I see people write very performance C code where it matters, while moving on quickly where it does not. C++ code is often highly templated with annoying compile times, but still often slow because it still does not use the right data structures, and the amount of instruction bloat by specializing everything does not help for anything which is not a toy benchmark.
If you need callbacks and generics, you're not writing performance code.
99% of code in the wild is comically inefficient and is doing the wrong thing, using way too generic data structures and algorithms for very concrete problems. C++ templates may be one way to make comically slow code faster by spending a lot of compile time. But it's often much quicker to just write straightforward concrete code that the compiler can easily optimize.
IMO C++ makes for slow programs for the sole fact that it compiles so slow (if you use its modern features), so you have much less time to actually iterate and improve.
If compilation is even more than 10% of the time it takes you to run your tests, you're probably not writing correct code. Compilation times don't even measure.
So every time you compile, you run your test suite? I don't. And you trust that I have experience writing and compiling programs too...?
It should be a goal to keep rebuild times around 1 second (often not quite possible, but 3-5 seconds, even for full rebuilds, is often realistic). I edit, compile, run, edit, compile, run. Editing and running can often take as little as 1-3 seconds, and I sometimes do it dozens of times working in a row, working on a single improvement. That's why there is a 1 second rebuild time goal.
In practice I often work on codebases I don't fully control, but when the build times are excessively high, I will complain and try to improve. Build times longer than 10-15 seconds break the flow, they are a significant productivity hit. But they are quite common with C++ codebases (it can also be bad with C codebases by the way, but C++ is typically much worse because of templates and metaprogramming which is very slow).
You run your code before running tests? IMO that's bad practice.
1 second, seriously? Even the Linux kernel is based on C, and it doesn't even have compilation times approaching that.
I guess I also work on a lot of big data projects, where getting results will take... 48 hours or so, so anything shorter than that is basically some sort of unit test or dry run... so in that context, compilation times do not even register on the things slowing me down.
Running the code immediately after making changes is the first line of testing. To run a huge test suite full of tests that are completely unrelated to the current changes would be stupid, it's a huge waste of time and energy.
Yes, seriously, have you ever written a project from scratch? A simple .c file with a thousand lines in it should easily build and start within 100ms. A compiler should be able to do basic parsing and codegen at 1M lines per core.
If your runs take 48h, of course you need a strategy to avoid noticing bugs only after dozens of hours running. You can't tell me that it is efficient to make changes and to wait for minutes or even hours before noticing that your code wasn't even syntactically valid, or maybe it did compile but your code had a small oversight and you need to start over building.
The Linux kernel is a HUGE project, one of the biggest around. Yes, a full rebuild takes a long time, depending on configuration. Incremental rebuilds do not, though.
I'm actually working on a Linux kernel module (distributed filesystem client), it's on the order of 40 KLOC. I can do a full rebuild in 10/15 seconds (debug/release), and that includes calling into the kernel's infrastructure and doing a lot of stuff that shouldn't have to be done. An incremental rebuild after changing a single .c file is about 3 seconds. Restarting the module (swapping for the newly built one) takes less than 10 seconds also. And this can be already a stressful bottleneck depending on the task. Say you're improving logging in a particular section of code, this can easily require 5-10 attempts.
I'm working on Desktop GUIs (2D/3D) too. You need a quick turnaround time as much as possible. Many changes are trivial but you want to do many small incremental improvements, recompile, run and test (manually), often with a breakpoint on the code you're currently working on.
The projects I'm working on are written in C or conservative C++, and most have from thousands to hundreds of thousands lines of code. They can be built from scratch in a short amount of time (< 10s for the smaller ones). And all of them do incremental builds in <= 10 seconds except when maybe changing the most central headers which essentially means a full rebuild.
You can also design a C/C++ codebase to always do a full rebuild, compiling everything as a single unit. That can be faster than trying to do incremental builds, for codebases of considerable size. Try out the popular raddebugger project, a complete build after checkout is about 3 seconds. It's ~300 KLOC I think.
It really depends on what you are doing. Sometimes I am not building in a day, just designing and thinking by editing. Sometimes I am refactoring for hours without building. But sometimes I am rebuilding every 10 seconds or so.
Writing a C++ parser is much harder than a C parser to the point there had been just 3 parsers used among all C++ compilers for quite a while. So you'd need to use some library for parsing. So now you are looking into the library's parser compatibility with the compiler you are using (it might not support the C++ standard you are on at all, it can have bugs preventing it from parsing the code that the compiler parses just fine) and not just on your code but on the library headers you include in your code. What are you going to do when cindex/libclang or whatever chokes on a libstdc++ header? You also have the issue with builtin macros: are they are the same in your library parser? Most likely not. Good luck testing all that.
Two-stage compilation is just a bonus on top: you add a sequential dependency in your build graph and if you have enough of these parsing programs you are going to wait till they are all built before your build can go wide.
Usually destructors can't fail. But for rare cases like with close() you can write a helper function (static method) which destroys the given object and returns an error if has one.
One reason is that c++ still hasn't gotten 'trivial relocatability' right - i.e being able to memcpy/memmove and not have to call constructors/destructors when growing your vector class.
Actually, issues with non-trivial moves and relocations are specific only for C++. Some other languages (notably Rust and Swift) don't have such issues, but still have nice automatic memory management via destructors and containers atop of it.
C++ compilers optimize-out empty destructor calls and sometimes even replace calls to move constructors/move assignment operators via memcpy. But it's unfortunately not guaranteed in all cases due to constrains of the C++ object/memory model designed initially without proper move semantics.
Yeah I dont think a single current LLM would fool me in a turing test - I would obiously use all kinds of prompt injection techniques, ask about 'dangerous' or controversial topics, ask about random niche facts in varied fields, etc.
Thats a good point actually, I hadn't thought about deploying hacks and jailbreaks in a turing test but thats exactly what should be done, if its being done adversarially
Having different types seems wrong to me because endianess issues disappears after serialization, so it would make more sense to slap an annotation on the data field so just the serializer knows how to load/store it.
I'm an embedded programmer who occassionally needs to write various windows programs to interface with embedded devices (usually via serial port or usb), and I find it a breeze to write native gui programs in pure win32 and c++.
Recently had to add a new feature to and old program that was last updated in the XP era and two things to note:
1. The program did not need to be updated to run on Vista, 7, 10 and 11, shit just kept working throughout the years.
2. I loaded the project into Visual Studio 2022, it converted from VC6 and compiled without problems, added the feature, shipped a new .exe to the customer, and it just worked.
What other platform has that backwards and forwards compatibility success story?
I feel like I'm the only person in the world who would rather write ugly win32 jank for the rest of my days than ever having to touch an "elegant" or "well structured" Cocoa codebase. In win32 if you want a button you call a function and pass a hande, in the Apple world you first subclass 7 interfaces in some unreadable Smalltalk-wannabe syntax and pray you don't need to access the documentation. And of course they constantly change things because breaking backwards compatibility is Apple's kink.
After bouncing around GUI toolkits (from win32 to SwiftUI) and web for 30 years I have simply run out of fucks. They all suck. Each in their own unique way. Apple aren't worth singling out - they are just their own special isolated variant of it.
Tcl/Tk is pretty good in terms of rapid development. Unfortunately it has stagnated quite a lot over the years.
Gtk on the other hand is absolutely terrible and its developers don't help by completely rewriting things every few years and breaking all existing code in the process.
Have you tried WinForms? It isn’t the latest hotness so Microsoft has to be dragged kicking and screaming to support it in current VS, but they were forced to do so because corporate developers still have some clout.
I still think that WPF was the peak desktop UI framework. Extremely powerful with lots of small composable primitives, can easily do declarative but drop into more traditional event-driven imperative style where it makes sense.
I live in a bizarro universe where I started my career working on an expansive WPF desktop app on .NET Framework 4.0, and am still working on it now on .NET 10. From my perspective it's been WPF the entire time, and it's been pretty okay.
Similarly, I've been doing It development for pretty much the entirety of my career. When I see the struggles to make remotely useable apps in other frameworks I'm very happy I chose this path
This is patently false. To add a button to your UI, you open your window’s nib file Xcode/Interface Builder, click the plus button on the toolbar, and add a button. Then you control-drag from the button to File’s Owner and choose the method that you want to invoke when the button is clicked. Done.
Yes, generations of Mac and Windows programmers have used GUIs to create their GUIs. Visual Basic, MFC + App Studio, .NET + WinForms, Interface Builder…
Why wouldn't you program a GUI with a GUI if one is available? Avoiding the use of WYSIWYG editors when making GUIs is like avoiding the use of musical instruments when writing songs.
> Why wouldn't you program a GUI with a GUI if one is available? Avoiding the use of WYSIWYG editors when making GUIs is like avoiding the use of musical instruments when writing songs.
I've been a developer for a long time; I've built pretty large applications in all sorts of technologies and I now just prefer defining GUIs using text. Having a live GUI preview is great but actually dragging and dropping stuff is not more efficient to me.
To fit your analogy, using source code is like writing a song using musical notation. I'll write the song, then play it, and then go back to notation to fix it or expand on it.
In my experience drag-and-drop GUI editors work great until you add the constraint that the window has to be resizeable. Then it becomes a mess of clicking through dialogs for every UI element to make sure the anchoring and display flow is set up correctly, and you often have to go back and redo this whenever you add a new element or change the layout of the form. I think the best GUI programming experience I've ever had is on Palm OS simply because it had the constraint that every program always displayed in fullscreen at 160x160 (pixel-doubled to 320x320 on later devices) so you never had to deal with resizeable windows.
I'm not saying you should never program with a GUI, but it comes at a cost of being able to read the code and tell what the result of the code will be, and all the associated benefits of version control and code reviews that you lose.
And as a side-effect of that, merge conflicts become murder when your "Fix right-hand margins" commit with a 20 line readable +/- diff instead becomes a 1000 line +/- diff.
The one time I built an iOS app using the xCode IB so that I could get up to speed more quickly, I really came to regret it several years into the project.
If this is a real issue, it seems to be an issue with the way a particular toolkit handles visual WYSIWYG editors. Lazarus serializes the object properties into a simple and readable text format that follows the class declarations themselves and you can easily diff them. E.g. here is the diff of a minor change i did (added a new button in a panel with an event handler and modified a property in an existing button):
I swear I meant to write an "often" in my previous post. But yeah, that was largely directed at the interface builder in xCode, for iOS - it gives you storyboard/xib files that are effectively-not-human-editable (and entirely non-mergeable) xml.
That feels like quite the exaggeration. If all you want is a button, all you need to do is initialize an NSButton and then tweak a few properties to customize it as desired.
If you want something more custom, subclass NSControl and you’re off to the races.
And if Obj-C isn’t your cup of tea, one can use Swift instead, even in a codebase that had been only Obj-C prior.
You can now use SwiftUI, which is, as of the latest version, quite stable. They used to change things a lot between releases a few years ago, but nowadays you don't need to refactor your code every year. Only issue with it is that it's iOS first, so you may need to fallback to AppKit (Cocoa) to implement more complex elements.
If you were doing "classic" Cocoa in the way it was intended, you wouldn't need to subclass anything for a simple button.
You wouldn't even need to write a single line of code, you'd just instantiate said button in Interface Builder, hook it up to a delegate (e.g. a window controller) and off you go. You can create a hello world example with a handful lines of code.
And even if you'd rather create the button programmatically, it's not much more involved.
Sure, if you're coming from Win32 and expect to program Cocoa without learning Cocoa, you're out of luck. But I guess that applies to all frameworks.
I'm probably another, but I have never done any professional Win32 work. You know, those kind of jobs are rare now and I doubt they want anyone without experience.
What are you talking about? In Cocoa if you want a button you drag one in via Interface Builder. You don't even need to write any code. If you want it to do something, you type the name of the function it should call.
To me this kind of "no need to change anything" implies stability but there's a younger cohort of developers who are used to everything changing every week and who think that something that is older than week is "unmaintained" and thus buggy and broken.
One of the earliest security issues that I remember hitting Windows was that if you had a server running IIS, anyone could easily put a properly encoded string in the browser and run any command by causing IIS to shell out to cmd.
I mentioned in another reply the 12 different ways that you had to define a string depending on which API you had to call.
Can you imagine all of the vulnerabilities in Windows caused by the layers and layers of sediment built up over 30 years?
It would be as if the modern ARM Macs had emulators for 68K, PPC, 32-bit x86 apps and 64K x86 apps (which they do) and had 64 bit Carbon libraries (just to keep Adobe happy)
I think its at least as much of a working environment preference.
Once I became experienced enough to have opinions about things like my editor and terminal emulator... suddenly the Visual Studio environment wasn't nearly as appealing. The Unix philosophy of things being just text than you can just edit in the editor you're already using made much more sense to me than digging through nested submenus to change configuration.
I certainly respect the unmatched Win32 backwards/forwards compatibility story. But as a developer in my younger years, particularly pre-WSL, I could get more modern tools that were less coupled to my OS or language choice, more money, and company culture that was more relevant to my in my 20s jumping into Ruby/Rails development than the Windows development ecosystem despite the things it does really well.
Or to say differently: it wasn't the stability of the API that made Windows development seem boring. It was the kind of companies that did it, the rest of the surrounding ecosystem of tools they did it with, and the way they paid for doing it. (But even when I was actually writing code full time some corners of the JS ecosystem seemed to lean too hard into the wild west mentality. Still do, I suspect, just now its Typescript in support of AI).
The one big challenge I've had with big legacy Win32/C++ codebases is migrating it fully from 32bit to 64bit. Loads of know-how and docs for complex GUI controls and structs are lost to time, or really fragmented. Other than that, yeah it really does all just work once you're past that.
Well it's still a 32 bit program so I guess that helps.
Would probably require some porting to make it 64 bit native, but as long as you use the WPARAM, INT_PTR typed and what not correctly it 'should just work'.
Yeah that's the bulk of the work for migrating small Win32 apps. Things escalate when someone has built their own dynamic GUI framework over Win32, used a range of GUI controls, and then built event-driven apps on top of that, it's a lot lol
I went through that a few years ago and it actually went pretty smoothly. There were a few UINT_PTR or DWORD_PTR changes I had to get used to and a couple of string glitches (we mostly used the _T() macro for strings and already used the _t variants of string functions in the original code, so that helped).
The biggest problems were DAO (database) and a few COM controls that were not available in x64.
How do Linux and Java do it, when you want to compile your program in both 16-bit char and 8-bit char mode? Oh that's right, you don't.
You can pick one or the otherfor Windows too, so don't ask me why it's done that way. It was originally so you could compile for both the new hotness Unicode, and the old compatible ASCII.
Partly because Microsoft resisted UTF-8 forever, and so using the ANSI/multibyte strings didn't therefore give you modern functionality. Why they didn't implement Unicode for Win95, I'm just not sure. If they had, the only reason to compile an ANSI version would have been to target Win32S (Windows 3.11).
Or, they could have implemented a UTF-8 code page for Win32 as soon as it was available and then most software could just use byte strings.
In 32-bit windows, you used to be able to see if a pointer was valid or not by seeing if it pointed to the last 2GB of address space. If it did, it was pointing to Kernel memory that was not valid for user mode code.
But then Large Address Aware (4GB limit) changes everything, and you can't do that anymore. In order for a program to be Large Address Aware, you need to not try to do things like check high bits of pointers, then every single library and DLL you use also needs to do the same.
Win32 programming has been reduced to a small niche now. Even 20+ year old Win32 books don't cover things in-depth (or practical use cases) let alone the 32bit->64bit migration
Yeah that doesn't always work that well. Think you were lucky.
Add high DPI to the mix and things get rough very quickly. Also the common control have weird input issues (try ctrl+backspace in an Edit control). All those little things need to be fixed carefully for something to be ok in 2026.
Winforms is great until you try to make windows dynamically sized, or deal with DPI nicely. In every other regard it's still fine, and for accessibility actually _better_ than many subsequent frameworks. And produces nice small fast executables.
I assume that if Microsoft hadn't abandoned WinForms for the next thing, it would support dynamic sizing and DPI properly. It's mindboggling how much time and effort they've wasted coming up with new GUI frameworks instead of just improving on what they have.
It does, but many still think it is like using VB 6 and don't learn the additional APIs that provide that support, e.g. FlowLayoutPanel and TableLayoutPanel.
Or, unless they've changed it, hardware accelerated rendering. Winforms was based on System.Drawing, which used GDI+, which was largely software rendering. This was confusing because GDI+ was not really related to GDI, which had and still does retain some hardware acceleration support. Even basic color fills start becoming an issue with a big window/monitor.
Winforms is also .NET based, so it's inaccessible if you don't want to write your UI in and take a dependency on .NET.
transparency as well. WinForm really struggles with the idea of stacking elements on top of one another where there is an arbitrary amount of transparency or tricky shapes. Its just not worth the hassle compared to WPF.
I've not done MFC Win32 programming since 1999 but if I recall those programs don't execute the main() function. They instantiate the Win32 class for your app or something like that. I can't remember any details anymore.
You still have a main function in Win32, it's just called WinMain and has a slightly different signature.
MFC has CWinApp, which you'd normally subclass, and a stock WinMain implementation that instantiates that, but it's not strictly necessary to subclass it, just convenient.
They don't use main because they use WinMain, which is the entry point for Windows apps that don't run in a console window.
WinMain should create an instance of the app class and call Run.
"You're posting too fast." I never got that before. How fast is too fast? I tried to post this comment hours ago, but I couldn't. I guess I've been restricted because of this comment https://news.ycombinator.com/item?id=47473604 which feels pretty unfair
> "You're posting too fast." I never got that before. How fast is too fast? I tried to post this comment hours ago, but I couldn't. I guess I've been restricted because of this comment https://news.ycombinator.com/item?id=47473604 which feels pretty unfair
I get that if I post more than six replies in an hour.
But that’s the point the article’s making. At the C level you’ve got a fully functional system. Above that level (even at the C++ level), feature support is a mess.
Honestly, your GUIs are too simple to be part of this conversation. Try writing something like Spotify in WinAPI and that's not even a complicated GUI either.
Actually it doesn't. Win32 skinning is either making a control completely from scratch or hacking into undocumented aspects of the native controls - i.e. what WindowBlinds does. AFAIK modern Delphi has some component that basically follows the WindowBlinds approach.
> Win32 skinning is either making a control completely from scratch
In almost all cases you just need to handle the drawing yourself, and you get fairly broad access through the API to do so through the various non-client and client window messages.
Now, Windows has gained some newfangled components over the years with more or less integration, I'm thinking basic Win32 controls.
What all cases? Windows (basic Win32 at that) provides a lot of controls like buttons, checkboxes, inputboxes, scrollbars, radiobuttons, comboboxes, listboxes, etc which do all their drawing themselves and you do not have any way to fully skin them (without hooking into undocumented stuff). Some controls allow for custom drawn elements but pretty much always that is just for the elements themselves, not the entire control.
Why should the first argument be so special? And how do you decide which struct should get method if you have a function that operates on two different types?
Any parameter list is permutation invariant. the first argument is special because methods are members of classes and thus get private access in the body of the method. If the implementation of the method is improved with private access then make a design decision.
You don't have to decide where to put the implementation if you dont want to, its just a type of inlining
I've been using C on a daily basis for 30+ years and name collisions has just never been a problem.
Granted, it might be due to lack of a package manager so micro dependencies ala import is_even is not a thing here, but still, in practice, no name collions occurs.