Rendered at 19:48:20 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
jalospinoso 5 days ago [-]
I wrote this after repeatedly seeing experienced C programmers hit the same sharp edges while moving into modern C++ codebases.
Many of these differences are intentional and defensible from the C++ side. But some are still surprising because they invalidate patterns that were historically common, performant, or idiomatic in C.
The interesting part to me isn’t "C vs C++," but where the languages diverged philosophically: object lifetime vs raw storage, stronger type systems, implicit conversions, ABI and optimization assumptions, and the boundary between "portable" and "works on my compiler."
I’d also be curious which C constructs people still genuinely miss in modern C++. For me, restrict is still near the top of the list.
uecker 2 days ago [-]
The "stronger type system" is mostly a myth in my opinion. It was true in the past in pre-prototype C. The void pointer rules are better in C IMHO as they avoid unneeded casts (that then remove more type safety) and FAMs and variably-modified types can express things C++ simply can't do well.
spacechild1 1 days ago [-]
I don't get your point at all. C++ has different casting operators (static_cast, const_cast, reinterpret_cast) that are strictly safer than C-style casts.
Also, let's not forget that implicit casts between unrelated pointer types is only a warning in C. Fortunately, modern C compilers started treating it as an error by default because it caused so much harm: https://gcc.gnu.org/gcc-14/porting_to.html. In C++ this was always a compiler error.
1718627440 7 hours ago [-]
> implicit casts between unrelated pointer types is only a warning in C
A warning in C has the meaning of a "stern warning" aka. "That very much won't work, I warned you!". An error means, "I literally, don't what you mean".
Also as far as I know, the C standard only talks about diagnostics.
skrebbel 1 days ago [-]
I don’t understand your point at all, C++ objectively has a much stronger type system. It’s turing complete!
I’m not arguing that that’s better, or worse, but it’s definitely true and by no means a myth.
lelanthran 1 days ago [-]
I don't think GP meant "it's completely made up", I think he meant the distinction doesn't matter most of the time.
I.e. most of the time the typing in real C++ code isn't meaningfully stronger than that found in C code.
uecker 1 days ago [-]
More complex != stronger. A weak type system would imply that the type systems forgives type mismatches. C had this before it imported prototypes from C++ where you could call a function without declaring it first and if you got it wrong you got some error or crash. The only part where I think C++'s type system is meaningfully stronger than C are enumeration types.
flohofwoe 1 days ago [-]
> The void pointer rules are better in C IMHO as they avoid unneeded casts
...so much this! A void pointer is an "any-pointer" by design. It shouldn't require casting from and to specific pointer types, that defeats the whole point of having void pointers in the first place.
TuxSH 1 days ago [-]
> It shouldn't require casting from and to specific pointer types
You don't need to explicitly cast T* to void* (guaranteed to be safe), you only need to cast when converting out of void*.
The rules are basically the same as casting between pointer-to-derived-class and pointer-to-base-class and they make sense.
uecker 1 days ago [-]
They make sense but reduce type safety, because once you add the cast the case might hide some real typing issue. I sympathize with the idea that the down-cast should be explicit though.
spacechild1 1 days ago [-]
> They make sense but reduce type safety
Yes, downcasting can be unsafe and should be used carefully, but what's the alternative? At least in C++ you can't cast between unrelated types without an explicit reinterpret_cast (or C-style cast).
chrchang523 1 days ago [-]
and you CAN use static_cast to convert from void*; this silently keeps working if you refactor the void* into a matching-type pointer later, while raising a compilation error if you refactor to a different-type pointer.
uecker 1 days ago [-]
Yes, a static_cast would be safe, but then most C++ seems to use a C-style cast because it is less clunky and then is less safe than the corresponding C code. The issue is not that the downcast is unsafe (it is, but once you have a void pointer you already accepted this), but that it becomes even less safe by adding a C-style cast.
It is also not clear what is gained by forcing programmers to add a cast. Void pointers should be used sparingly anyway.
spacechild1 1 days ago [-]
> but then most C++ seems to use a C-style cast
If C++ programmers do not use modern safety features, that's really their fault. C-style pointer casts should be flagged in code review.
> but that it becomes even less safe by adding a C-style cast.
At least in C++ there is a safer option. If you really want to be on the safe side, you can even to a dynamic_cast (assuming your code base allows RTTI).
> It is also not clear what is gained by forcing programmers to add a cast.
I think the point is to make it explicit and stand out.
account42 7 hours ago [-]
Lack of ergonomics around static_cast is language problem. The "most safe" of the casts should not be this verbose.
safercplusplus 1 days ago [-]
You might be interested in the scpptool feature to help convert C code to a subset of C that will also compile as C++ (under clang++ at least) [1]. While many of the necessary modifications are fairly trivial, some of them aren't completely so. For example, C++ does not allow `goto`s that would skip over the declaration/initialization of a variable that would be accessible after the jump. So getting the C code to work as C++ can involve some (automatic) code restructuring.
Another annoying detail is that C++ doesn't seem to like forward references of `enum`s. That is, while
struct A* a_ptr;
is fine in both C and C++ even before `struct A` has been defined, apparently
enum A* a_ptr;
is not cool in C++ until after `enum A` has been defined.
One arguable benefit of keeping your C code compatible with (or at least convertible to) C++, is that you can theoretically use scpptool's auto-translation feature as build step to produce memory-safe executables from C code via transpilation to a memory-safe subset of C++.
I appreciate that restrict isn't there, because it is yet another UB source, programmer knows not to do errors kind of attitude, and secondly no one seems to care enough to write a language proposal for it.
m_mueller 1 days ago [-]
I take it you probably never tried to use any of these languages for HPC. Without a language standard, you have two options there to compile decently performant executables: (1) compiler pragmas, (2) give up and drop to assembly code.
I should add here that there's also (3): Switch to Fortran, which made fundamentally different choices and is IMO the only fully supported higher-than-C level language that can produce HPC applications without fighting a compiler left and right.
pjmlp 1 days ago [-]
Long time ago at CERN.
There are some ATLAS TDAQ/HLT papers with my name on them.
Template metaprogramming, multi-threading, and custom IP protocols where much more relevant.
einpoklum 1 days ago [-]
Can you link to some writeup regarding how Fortran is preferential to C++ (or rather C++ plus compiler `__restrict`) in this respect?
m_mueller 1 days ago [-]
I keep looking around and not finding any, so let me just try here before someone just takes it and slopifies it:
* built-in multidimensional arrays with efficient storage.
* related to this: built-in array intrinsics
```
real, dimension(100,100) :: A, B, C
C = A + B
```
this kind of code is already a close-to optimal "naive" implementation (not considering parallelization). so you start already at a solid place. then you can easily run it in parallel without too much specialized knowledge with OpenMP, OpenACC, MPI or even CUDA. the only thing you really need to be aware of when implementing your own loops/kernels: the intrinsic storage order, to optimize for cache hits.
* crucial: all the above amounts to a standard/best practice about how data is structured and formatted. everyone just uses the built-ins. Thus, interoperability between native Fortran numerical libraries is usually a complete non-issue. Meanwhile, Cpp has a fractured ecosystem with different array/vector types for its libraries. Converting between one and the other is usually a no-go.
* next, the intent plus pass-by-reference system. it combines IMO the best of both worlds of a functional vs. procedural approach:
- I can predefine if an array / variable is intended as input, output or both, simply with `intent(in)`, `intent(out)` etc.
- compiler can thus do checks for me if I'm breaking a contract.
- yet, since I pass by ref, I don't have to worry about memory not being used efficiently. This really matters once you deal with GB, TB or even PB worth of data going into a simulation over time - there's just no way to deal with that in a purely functional way.
- only where really necessary, you can still drop down to pointer semantics, e.g. for the outer glue code of a simulation that swaps outputs and inputs for the next time step.
- having `restrict`-by-default semantics here helps immensely. Imagine you have many arrays with lots of data to deal with, and your kernels access always several at a time to do calculations. In Fortran I can write it intuitively, while in C/C++ I must remember to specify `restrict` or to preload all point-inputs first into separate variables to direct the compiler. Otherwise the memory pressure increases, and by far most such physics simulations are memory bandwidth bound - every access counts.
* finally, a clean symbol definition system that decouples types from byte lengths. a `float` in fortran is just `real(4)`, a double is `real(8)`, a long int is `integer(8)` and so on. now, it's trivial to do a bit of preprocessing to switch the precision.
However, the last part is where Cpp has a strong advantage: Well supported meta-programming (generics, templating or even just well supported pre-processors). Fortran's compilers come with a lot of built-ins, so the lack of these is less of an issue than you might think, but it's still a limiting factor. All that being said, a typical scientist doesn't tend to care and just wants to solve a particular problem rather than thinking in generalized frameworks - and that's why I find Fortran still serves them better for numerics than anything that came since.
hgs3 2 days ago [-]
Not sure if you're aware, but defer is proposed for C2Y [1]. It's already available in Clang behind a compiler flag. It is interesting how the languages continue to diverge.
C++ is 1990's Typescript for C++, while C folks still think is a portable Assembly instead of designed to an abstract machine model.
As such C++ community embraces high level abstractions and type systems improvements, whereas C wants to still code as targeting classical hardware.
wasmperson 24 hours ago [-]
> C folks still think is a portable Assembly
> C [community] wants to still code
> many still don't know to distinguish
> the culture that... despite easy proof that isn't the case
> devs wrongly assume
> self inflicted complexity
> considered an advantage when argued by C folks
> when the same crowd points
> as the C crowd pretends it to be
You're arguing in this thread not by addressing what people are actually saying but by bringing up some hypothetical version of what "the C Community" thinks, then arguing with that.
jstimpfle 2 days ago [-]
Caring for the actual assembler output in selected critical pieces of code is not the same as ignoring the abstract machine model. What you claim is simply not the case if you check actual proficient systems programmers. Of which there are an astonishingly high share C and C++-but-mostly-C programmers.
pjmlp 2 days ago [-]
Any user of compiled languages cares about Assembly, which is why regardless of the compiled language, an Assembler was always shipped alongside.
Also it isn't a C invention to have the compiler dump the Assembly output instead of object code.
Now the culture that C language constructs in 2026 are still 1:1 to Assembly instructions, that pretty much prevails, despite easy proof that isn't the case at various compiler optimization levels.
Proficient devs, well many still don't know to distinguish what is their compiler, and what ISO says.
jstimpfle 2 days ago [-]
It is the case that you can more easily know what happens when you don't use the wrong abstractions but stay in control. Highly-abstracted C++ code basically makes allocations and syscalls in the whitespace between the source code tokens.
You can't do systems software like that, you have to roll back the abstractions and roll back the use of pre-canned containers and libraries that you don't understand.
So it's all about understanding and control, not about some idea that C was defined in terms of assembly instructions, which it obviously is not. That's a total strawman.
pjmlp 2 days ago [-]
Except modern C also has plenty of abstractions, devs wrongly assume it doesn't.
Then get surprised when it doesn't map to the SIMD/SIMT NUMA machine their code actually executes on.
uecker 2 days ago [-]
There is not much real evidence for "devs wrongly assume" and as someone writing numerical code (clusters, NUMA, SIMD, etc.) I think C is still the ideal tool for this.
pjmlp 1 days ago [-]
Why should it be, the language doesn't expose any of it.
uecker 1 days ago [-]
Assuming you mean the standard does not provide features for numa and simd? It doesn't necessarily have to. I think it is not surprising that you seem always bewildered that people still use C (as per your comments), as it seems you fundamentally do understand neither standardization nor systems programming.
pjmlp 13 hours ago [-]
I do certainly understand it, by the eyes of C++, Rust, Ada, Object Pascal, Modula-2, D.
jstimpfle 9 hours ago [-]
Systems programming is not primarily about programming languages though...
jstimpfle 2 days ago [-]
such a strawman again... You don't want to be writing explicit platform specific SIMD most of the time. You just want to write a dumb function that doesn't do any non-obvious calls, doesn't cause thread contention, doesn't hide complexity, isn't going to be a nightmare to change later, no surprises.
I am talking about self-inflicted complexity that is entirely within the C(++) machine model. Avoid that complexity and you're pretty good already. Only drop down to concrete hardware arch level where it makes sense. But largely, the C machine model is still very much suited as a model for actual hardware. Writing straightforward obvious code allows you to stay in control of memory layout and the data transformation paths. It easily gets you within <<2x of what you could achieve with hand coded assembler for the >90% of the code that are pretty boring and straightforward. And obviously you couldn't get the work done in time when coding everything in assembler.
pjmlp 1 days ago [-]
So abstractions are desired then?
I have seen plenty of self inflicted complexity in C, starting in the golden age of Yourdon Structured Method, and all those libraries that replicate C++ basic features with preprocessor macros.
jstimpfle 1 days ago [-]
Most of these methods and ideas turned out to not work, neither in messy C libraries, nor in C++ or other languages.
gritzko 2 days ago [-]
That is the entire point, yes. Reasoning about layers of completely imaginary entities is what demotivates me about C++ and Rust. Meanwhile, hardware bits are very real (and getting more expensive recently). Having implemented slices and generics in C, now C++ feels like Vietnam flashbacks.
Yet C23 isn't K&R C any longer, nor is the hardware a PDP 11.
Also when we eventually start talking to agents that perform the whole execution steps by themselves, that is kind of irrelevant.
Except for the lucky ones that still code to keep the infrastructure going, which is mostly C++.
flohofwoe 1 days ago [-]
The PDP-11 myth is getting a bit tired by now ;)
If C would be so hardwired to the PDP-11 architecture it would have died with it. In reality C works just fine on all sorts of hardware (like GPUs) with only minor extensions.
1718627440 7 hours ago [-]
Yeah, a continuous memory model is used, because it is convenient, not because some old hardware happen to use it.
pjmlp 1 days ago [-]
Just like plenty of other programming languages.
I am also tired that language extensions in C to work around ISO defencies is considered an advantage when argued by C folks, while at the same time it is considered a language design fault when the same crowd points to other programming languages.
uecker 2 days ago [-]
The "nor is the hardware a PDP 11". Byte access was the main new feature of the PDP 11 that C adopted. Are you saying being able to access individual bytes is not relevant on modern hardware?
shakna 2 days ago [-]
Might more mean that we've standardised on a few things like what a byte even is.
The PDP-11 had both 8 and 9-bit bytes. Thats a complexity that few programmers have to touch on, today.
elch 1 days ago [-]
IIRC PDP-11 was a 16 bit word machine with an 8-bit byte. Maybe you remember PDP-10 with 4x9=36 bit words?
uecker 1 days ago [-]
Anyway, I do not see how this affects the design of C in a way that makes no sense anymore today (except that one could require CHAR_BIT to be eight, but there are still DSPs where this is not the case). I think people repeat the "the C design reflects the out-dated PDP-11 hardware" meme because it sounds smart while in reality it is just nonsense.
pjmlp 1 days ago [-]
So when is WG14 standardising modern hardware into the C standard?
Basic stuff like SIMD, SIMT, without requiring users to go beyond language extensions, something that any programming language can offer in similar capacity?
1718627440 7 hours ago [-]
Why does the language standard need to prescribe everything. It is a standardization of between existing compilers, they are allowed and intended to invent things. If anything the experience from C++ has shown, that writing too much behaviour into the standard accidentally has consequences on the possible performance. The C standard is descriptive, not prescriptive.
> something that any programming language can offer in similar capacity?
By your measure a lot of other languages don't offer anything to begin with, because they do not have a standard at all, only a reference implementation.
uecker 1 days ago [-]
Ah, the "what is not standardized does not exist" argument again.
shakna 1 days ago [-]
On the 11, the UNIBUS was 18 bit, the program space was 16 bit, and addressing was 22 bit. So it depended if you were using I-space or D-space.
zabzonk 1 days ago [-]
Actually, if you were mad enough to use the feature, the Dec10 had 6-bit "bytes" - 6 to a word.
pjmlp 1 days ago [-]
It is, however hardly something unique to C, as the C crowd pretends it to be.
flohofwoe 1 days ago [-]
> I wrote this after repeatedly seeing experienced C programmers hit the same sharp edges while moving into modern C++ codebases.
...I've seen this more often in the opposite direction. Since C++ is stuck with a ca 1995 non-standard subset of C, C++ coders usually have a very outdated view of C.
> I’d also be curious which C constructs people still genuinely miss in modern C++.
Not implementing the full C99 designated init feature set was a huge missed opportunity in C++20. Every single feature of C99 designated init is important and clicks with the other features and the rest of the language, take one or two away and it becomes mostly useless (e.g. the order requirement in C++20 means that designated init is only useful for trvial structs).
It's especially tragic because Clang already had the full C99 designated init feature set in C++ mode implemented long before C++20 and it worked just fine.
> The interesting part to me isn’t "C vs C++," but where the languages diverged philosophically
IMHO this "schism" was completely unnecessary and only happened because of ignorance and hubris by the C++ designers. Objective-C shows that C can be extended with radical new features but without messing up the "C side" (e.g. ObjC features don't overlap with C features, which means that ObjC is automatically compatible with the latest C standards).
In the end it's not a big deal of course, C and C++ are now entirely different languages and longterm that's for the better. Even the C++ peeps seem to have come to that realization and no longer recommend to "compile C in C++ mode" (like Herb Sutter in 2012 when trying to justify why MSVC had no C99 support: https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...):
"We recommend that C developers use the C++ compiler to compile C code (using /TP if the file is named something.c). This is the best choice for using Visual C++ to compile C code."
This was bad advice back then and is even worse advice today. At least MSVC got "good enough" C99 support a couple of years later (in VS2015), but after a few hopeful years after 2019 it looks like MSVC development has completely stalled again.
fuhsnn 1 days ago [-]
> IMHO this "schism" was completely unnecessary and only happened because of ignorance and hubris
Having attempted to implement it correctly in slimcc, there are indeed some edge cases[1][2] that justify not adapting it fully.
[1] Unordered side effects; evaluation of expressions with overlapped destination is implementation defined but not listed as such (the wording in standard is "can potentially not be evaluated").
Because Microsoft has been focusing on improved C# for low level coding, see recent update on memory model changes for C# 16/.NET 12 roadmap, Rust adoption, and good enough C++ support.
Following on the Secure Future Initiative activities.
The C updates have been what is required to compile critical FOSS projects, or support big name customers on Windows.
Apple and Google are also not racing to adopt new clang versions on their platforms.
zabzonk 1 days ago [-]
> This was bad advice back then and is even worse advice today.
The languages have diverged a lot, it's true. Still, it is worth noting that all the code in TCPL 2nd Ed was compiled with Stroustrups C++ compiler, as there wasn't a C compiler available. Source: Preface/Acknowledgments.
aw1621107 1 days ago [-]
> It's especially tragic because Clang already had the full C99 designated init feature set in C++ mode implemented long before C++20 and it worked just fine.
How did Clang handle differences between member declaration order and the order in which initializers appeared?
(the warnings in C++ mode had only been added after C++20)
aw1621107 11 hours ago [-]
Hrm, I take it that was considered too footgun-y for the committee?
crest 1 days ago [-]
About half of them read as "I tried to use C++ as a worse C" e.g. using struct initilisation instead of constructors, using malloc instead of new or new[].
My pet peeve with C++ is that the sequence point operator can be overloaded at which point it stops being a sequence point.
chabska 1 days ago [-]
As the title indicate, this article is comparing construct-to-construct, not idiomatic code to idiomatic code. You probably won't use struct initialization in C++, yet the feature still exist, so it may be useful to someone to compare it to the similar feature in C.
spacechild1 1 days ago [-]
What's the "sequence point operator"?
twoodfin 1 days ago [-]
,
spacechild1 1 days ago [-]
That's the comma operator. I didn't know you could overload it! That's pretty crazy. However, I have never seen anyone do that. Do you have any real world examples?
addaon 21 hours ago [-]
See e.g. the very-popular Eigen library, in which the type CommaInitializer basically exists for the sole purpose of overloading `operator,`, allowing a cleaner matrix initialization syntax.
Did you use an LLM to write this comment? (I don't mean this as an accusation, I'm uncertain. I'm just trying to calibrate myself.)
Edit: I should've had more conviction in my instincts, this is slop.
DuckConference 1 days ago [-]
It didn't stand out to me on first read but pangram gives it a high confidence rating as being written by AI.
Retr0id 1 days ago [-]
It's much more obvious when you look at the user's comment history.
Curiously my comment above was on +3 karma last time I looked, but now it's on -2. It seems like the median HN user is getting worse at slop detection (or is otherwise ambivalent towards slop comments).
acontopo 1 days ago [-]
> isn’t "C vs C++,"
Perhaps be more careful in trying to make LLM output look like you wrote it yourself. The incongruent punctuation mark types, with curly apostrophes and straight double quotes mixed together in the same text, are a dead giveaway.
mjs01 2 days ago [-]
Some unmentioned incompatibilities I've encountered that makes a C header not directly usable in C++:
- C `_Atomic(T)` and C++ `std::atomic<T>`. C++23 has C compatible header `stdatomic.h` that defines `_Atomic(T)`, but it's still problematic
- C `_Noreturn/noreturn` and C++ `[[noreturn]]`. C23 `[[noreturn]]` makes them compatible
- C inline and C++ inline are different. Good news is their `static inline` are the same
- C has anonymous struct. C++ doesn't. Both have anonymous union though
mike_hock 2 days ago [-]
The thing with the flexible trailing array member is a C++ design flaw. Now the fix wouldn't be to allow those "flexible arrays" in C++, at least not the way C has them, but it should have a concept (not that kind of concept) of types that are indeterminately sized at compile time and whose size is determined at construction.
If you're allocating something on the heap anyway, you shouldn't be forced to pay for an indirection in order to have some variable-sized data in the object, you should just be able to put it all in the one allocation. (Sure, you can achieve that with placement new hackery but that certainly isn't "idiomatic" C++.)
Of course that's completely incompatible with the way allocation and construction work (storage has to be allocated before the constructor runs). Hence "design flaw" rather than "missing feature."
mgaunard 1 days ago [-]
There is no problem putting objects past the end of another object in C++.
I use this approach as part my zero-copy serialization library for what I call "out-of-line" sequences.
It does require smart usage of std::launder to be standards-compliant though.
For me this is the most important initialization in C that helps with clarity so much, I used mostly structs to have function parameters intialized like this
However C++ had at time no default initialization for unmentioned fields, so in 2017 I had to remove it when converting the code to C++
1718627440 7 hours ago [-]
Yeah you can even create linked lists of static data that way, as you can very well just take the pointer of an object here.
1 days ago [-]
einpoklum 1 days ago [-]
Designated initializers were unofficially supported by GCC (and clang IIANM) since around when C++11 was supported. See:
only if you specifically ask to get an error, would you actually get it.
cmovq 2 days ago [-]
Designated initializers is one area where C feels much more expressive than C++. And that feature has been standard since C99.
Panzerschrek 2 days ago [-]
restrict in C++ can't work well. One can mark pointer parameters with this attribute, but in C++ it's not recommended to pass raw pointers, std::string_view or std::span should be used instead, but there is no way to specify restrict for the internal pointer of these containers.
arcadialeak 2 days ago [-]
> std::string_view or std::span should be used instead
That is for when the owner is a std::string or an owning range respectively. But a raw pointer does still make sense as a non-owning view over a single element, doesn't it? I'm new to C++ so I might be wrong.
cocoto 1 days ago [-]
Non-owning view over a single element should simply be a reference, you don’t care where this element is located.
1718627440 7 hours ago [-]
That won't work with NULL.
maxloh 1 days ago [-]
I think the problem is that C++ is a poorly designed language with a fundamentally flawed development process.
Instead of letting compiler implementers decide which features to add and how to implement them, C++ employs a standards-first, top-down approach. Features are often defined by committee members who may not use modern C++ in their daily workflows, leaving it entirely up to the individual implementations to catch up.
Some features were standardized back in 2023, yet not a single implementation supports them in 2026.
Rust is impl-first bottom-up and it's stuck with a single implementation and GCC for Rust is still in the works, meanwhile C++26 reflection is already in GCC trunk.
zamadatix 1 days ago [-]
C++ has had it's own long periods of badly lagging/buggy implementations of the standards. It's better today but I'm not sure how much of that I'd credit/discredit that to the way the standards process works. So long as there is one quality implementation it doesn't matter much anyways.
bitwize 1 days ago [-]
Rust is also the systems programming language of the future. They're pretty much doing everything right.
uecker 1 days ago [-]
What IMHO Rust does not get right and why I do not use it: long compilation times, high complexity, its syntax, polymorphism based on monomorphization, the requirement for many dependencies to get anything done, an ecosystem susceptible to supply-chain attacks, no ISO standard.
tialaramex 1 days ago [-]
> no ISO standard
I'm sure I could argue with you about the actual technical differences but this part in particular is very, very stupid.
JTC1/SC22 † shouldn't exist at all. A committee structure is a bad way to do this work, and the practice of having periodic meetings - exclusively in person for much of the time these existed - actually makes it less rather than more useful.
ISO mandates a bunch of rules and procedures which surely make some sense if you're agreeing on thread dimensions for oil pipelines but are completely inappropriate for this work and yet because they're ISO committees the WG14 and WG21 processes are captured.
I don't think it makes good sense to use an SDO for this work, but if you must have an SDO for some reason beyond ego then you could do a lot better than ISO. Check out TC39 for example.
† The C and C++ standards committees are respectively Working Groups 14 and 21 of the Sub-committee on Programming Languages, SC22, of the (First and only) Joint Technical Committee between ISO and the IEC. Yes it's committees all the way down. "This programming language standard could have been an email".
uecker 1 days ago [-]
It doesn't have to be ISO, but some organization which follows some official rules and not some entity dependent on industry donations.
tialaramex 10 hours ago [-]
LOL. You do know ISO/IEC 29500 exists right?
ISO agreed that despite there being an existing, popular, broadly supported and open XML document standard they should define Microsoft's proprietary alternative OOXML as an international "standard". They even held votes repeatedly until the voters gave the "correct" answer... no worry about "industry donations" there.
uecker 7 hours ago [-]
I remember the story, but it was considered outrageous for a reason.
tialaramex 6 hours ago [-]
I'm sure it would be considered "outrageous" when the same happens outside an SDO too.
uecker 6 hours ago [-]
Well, one could consider Mozilla's management of firefox to be outrageous, but then, Google pays, so what to expect?
nicoty 1 days ago [-]
Out of curiosity, what do you think is wrong with monomorphization-based polymorphism? The other alternatives I'm aware of are 1. type-erasure via v-table based dynamic dispatch (which Rust also has in the form of the `dyn` keyword), which has performance and memory-allocation overhead and 2. macros, which Rust also has and, if used for polymorphism, would essentially be like compile-time monomorphization, but clunkier.
Maybe I'm missing something though and there are other alternatives done differently in other languages?
uecker 1 days ago [-]
The correct choice IMHO is type-erasure. It does not necessarily have overhead, because optimizers can specialize or devirtualize. Of course, this my depend on how you implement your language, but in C this works nicely. The problem with monomorphization is that it leads to exponential expansion, which later passes of the compiler can not unify again (at least this is much harder than not expanding in the first place). It should also fundamentally limit what you can do, because expansion has to stop at some point, but I haven't thought about this too much.
I also think that where you want monomophization, macro seem fine. I do not think this necessarily has to be clunky, but this is just a guess.
tialaramex 1 days ago [-]
I don't think the numbers bear out that "this works nicely" in C, it seems like you have worse perf numbers for some common cases like sorting ?
uecker 1 days ago [-]
Not sure what numbers you are talking about. If you use qsort from the C library the comparison function will not be inlined, but if you provide your own, this is no problem.
tialaramex 11 hours ago [-]
> if you provide your own, this is no problem.
If just "providing my own" would help why wouldn't the stdlib benefit too? You're going to have to spell out what you think can actually work here if you want me to believe there's "no problem".
uecker 7 hours ago [-]
It would also, but nobody cares enough because qsort is already fast enough for most things, and if you cared it is simply enough to do yourself. Are you doubting that C compilers can devirtualize function calls? Here is a small example that illustrates this. The compiler dervirtualizes all calls than folds the result: https://godbolt.org/z/E6cMMr8vx
tialaramex 5 hours ago [-]
[dead]
fluffybucktsnek 21 hours ago [-]
Type-erasure does have an inherent overhead. Sure, optimizations can be made, but they can be fickle and specialization is basically implicit monomorphization.
Using C macros to replicate Rust's monomorphism has several drawbacks: they are inherently unhygienic, even in comparison to Rust's own; you can't set type-bounds; they aren't even a part of C proper, etc.
I prefer Rust's approach with the choice between generics, macros, dyn and Any.
uecker 6 hours ago [-]
What inherent overhead does type-erasure have? The optimizer can always specialize just like for monomorphization. The difference is that it does not have to do this. Monomorphization specializes everything before the optimizer even has a chance to look the the code. So it fundamentally can not have advantages, only restrictions.
C macros are certainly a proper part of C and one can also certainly add type-bounds. But yes, they are not ideal. Still, if one wanted to do this, one could certainly improve them a lot for type-generic programming. I would prefer this to having macros, generics, a const expression sublanguage, and vtables.
fluffybucktsnek 3 hours ago [-]
The inherent overhead of type-erasure is the vtable. Which is funny, because you said you prefer one over the other at the end, when they are basically synonymous. The compiler may choose to devirtualize and specialize, but that isn't guaranteed. If you want this behavior, &dyn does the exact same thing.
Specialization provides more opportunities for optimizations, whereas proper type-erasure (which bars specialization optimizations) doesn't due to lack of type information.
"C macros" are a part of the preprocessor, which runs before the actual C compiler. As such, it lacks all semantical information the C compiler would have at that point, such as function implementions. In practice, macros in C serve two purposes: manipulating C source code (which Rust macros can also do, but with more hygiene); specialization polymorphism, but worse (in which both Rust's generics and C++ templates do better).
uecker 2 hours ago [-]
The vtable can be removed in all cases where you specialize by monomorphization, so the overhead compared to it is not really inherent.
Type-erasure can be undone by specialization. In contrast, monomorphization specializes the code first, at which point it becomes much more difficult to unify it again.
Sorry, my comment about preference was badly phrased: What I meant is that I prefer to make macros better, than having all the different but partially overlapping techniques in the same language. This is the complexity I am complaining about in both C++ and Rust.
fluffybucktsnek 2 hours ago [-]
I just noticed you said "The compiler can always specialize". That's indisputably false. That only happens if the compiler has enough information to infer the concrete type at the call point. Conditionals, collections, or anything that may throw doubt about original type info and their function implementations can (and normally will) disable specialization.
uecker 26 minutes ago [-]
It can always specialize where you could do monomorphization.
Someone 1 days ago [-]
> its syntax
I haven’t used rust, but having gotten used to C and C++ at a time, I expect that would happen with rust, too, if I started using it. Because of that, I think this is just a matter of familiarity.
> polymorphism based on monomorphization
Implementation detail (yes, there is only one implementation at the moment, but this means it can be changed without changing the language)
> the requirement for many dependencies to get anything done
Fixable if a party is willing to write or package together a fairly large set of dependencies into a single package.
> an ecosystem susceptible to supply-chain attacks
Fixable if that party is trustworthy. Also, for which languages is this less of a problem? You either have third-party libraries and a potential security problem, or you don’t, and need to write more code.
uecker 14 hours ago [-]
Some of Rust's problems may be fixable, but they are not being fixed at the moment and with something as complex as Rust, this is unlikely. I do not think monomorphization is an implementation detail, rather than a fundamental language design mistake that is difficult to correct.
pjmlp 13 hours ago [-]
Except for compilation times, no it isn't the complexity of the language, it is the toolchain.
See Ada, D, Delphi,....
Granted, priorities, one cannot fix everything at the same time.
zarzavat 1 days ago [-]
Is that supposed to be a bad thing? I like having only one implementation. Multiple compilers is annoying for users, have to write "portable" code which can only target the lowest common denominator. Only when a feature ships in Clang, GCC and VC++ can you use it. Each compiler needs its own flags/project as well.
Loosely coupling a language to its compiler is 20th century thinking for when programming languages were simple. It works for C because C is simple enough to be implemented over and over again. But for today's hyper complicated languages, multiple implementations is a pain for everyone.
asddubs 1 days ago [-]
It's a bad thing if you want your language to be truly portable and be able to work on anything
NooneAtAll3 1 days ago [-]
yes monopolies are always a bad thing
zarzavat 1 days ago [-]
A programming language with a reference implementation is not a "monopoly", it's a standard. By having a common implementation to write code against we can avoid wasting time on pointless minutiae such as which features are portable in practice and which are not.
When a programming language community fractures into multiple incompatible implementations everyone is worse off.
skrebbel 1 days ago [-]
> committee members who may not use modern C++ in their daily workflows
Are you sure about this one? I don’t know exactly who’s in the committee these days but last I checked they were all hardcore C++ programmers with decades of experience from the trenches.
tardedmeme 1 days ago [-]
I don't think that's true? Nothing gets in the standard without substantial actual experience on at least an experimental compiler branch first. It's just not always your compiler.
pjmlp 13 hours ago [-]
C++11 GC, modules, contracts, linalg are good examples of not having a substantial actual experience before ratification.
Now, I rather have them than not, but it is still clunky.
mrec 1 days ago [-]
`export template` was included in the C++98 standard without any experience, experimental or otherwise. The first implementation was achieved by EDG after enormous pain in the early 2000s, and their advice for any others attempting it was "don't". I'm not aware of anything else quite that egregious, though.
There were compilers that used the template export implementation, but not with that exact syntax. I think that's how cfront did templates.
spacechild1 1 days ago [-]
The one big and tragic exception being modules...
saalweachter 1 days ago [-]
> Features are often defined by committee members who may not use modern C++ in their daily workflows, leaving it entirely up to the individual implementations to catch up.
Eh, all of the committee members I've known are obsessed with modern C++, and "can this feature be implemented?" is definitely a blocker; numerous features got kicked down the road from C++0x to later versions because compilers weren't ready for them.
einpoklum 1 days ago [-]
C++ is a language with perfect 30-year backwards compatibility, and which mostly-maintains compatibility with another language it forked from (C), after 40 years of diverging development.
Rust is a language which isn't backwards-compatible, and certainly not compatible with source code in other languages.
Now, sure, Rust has its advantages, but - how can you fault C++ in the context of compatibility?
lepicz 1 days ago [-]
this compatibility sucks sometimes.
for example you want to add nice feature to c++ with nice syntax, but there is a similar syntax somewhere in C that nobody uses, but you have to support it. you end up with nice feature with horrible syntax.
weinzierl 1 days ago [-]
This is so true and it's a danger for other ecosystems too. Once the big corporations throw their weight in (be it in a committee or only informally) they make their use cases dominant regardless how niche they might be for the rest of the world.
For example in Rust there is one big entity that currently pours a lot of energy into improving C++ interop. Now, this is not exactly a niche topic, but especially in a world where AI makes many rewrites possible that we wouldn't have daunted to think about a couple of years before, we shouldn't waste too much effort to save legacy companies enormous codebases at the detriment of our preferred language.
uecker 1 days ago [-]
I think Rust has similar flaws as C++: too much unneeded complexity and abysmal compilation times.
tcfhgj 1 days ago [-]
unneeded complexity such as?
AdieuToLogic 2 days ago [-]
From the article:
In 2019 I wrote a short survey of C constructs that do not
work in C++. The point was not that C is sloppy or that C++
is superior. The point was that C++ is not a superset of C,
and that C programmers crossing the border should know
where the checkpoints are.
C++ was a superset of C 30-ish years ago. Now, as the author correctly identifies, it is not as both have taken different evolutionary paths.
electroly 2 days ago [-]
30 years ago, in C89 and pre-standard C++, it was the case that `int foo()` in C is a function that accepts any parameters, and in C++ it is a function with no parameters. In C89 you have to write `int foo(void)` if you want no parameters. This counterexample to C++ being a superset of C was well-known even back then.
Another well-known counterexample is implicit conversion from void*. In C89 you can do `int* foo = malloc(100);` but in C++ it requires an explicit cast from void* to int*.
I don't believe there was ever a time, even pre-standardization, when C++ was a strict superset of C; it always had little incompatibilities here and there.
avadodin 1 days ago [-]
Perhaps in c-with-classes(Cpre)? To the extent that its output could be considered C.
It looks like you're right and the answer to when was C++ a superset of C may well be "never".
From the description, Cfront had always been a full-fledged parser that only happened to output C since the very beginning.
zabzonk 1 days ago [-]
> a full-fledged parser
perhaps more accurately a fully fledged compiler (that emitted C)
pjmlp 2 days ago [-]
Already in C++98 there were differences.
?: has another execution priority.
Implicit cast scenarios are reduced in C++.
king_geedorah 23 hours ago [-]
Is there some kind of ambiguity inherent in designated initializers such that the field ordering requirement is necessary for C++ or is it rather just a quirk of the compiler authors’ choices?
ozgrakkurt 1 days ago [-]
You can pass a flag to clang to allow reordering field initializations in designator initializer thing. It makes the syntax super annoying in case of large structs anyway.
It doesn't matter unless you are using constructors or modifying some variables in the initialization expression anyway.
2 days ago [-]
dhruv3006 2 days ago [-]
> restrict: a C promise, not a C++ contract
This takes the cake.
einpoklum 1 days ago [-]
I find variable-length arrays (i.e. arrays whose length is defined at run-time and typically live on the stack) to be kind of dangerous, and try to avoid them, even in C.
Findecanor 1 days ago [-]
A type of VLA I would like to see more of in C code however, is variable-length array parameters (void func(int n, int arr[n]);) and arraypointers (int (*my_array)[n];).
Compilers can often do some static bounds-checking of such arrays.
But because those features had been introduced together with variable-length array stackvariables , people have lumped them all together and thus shunned these features that could otherwise have added safety.
BTW, one thing you should never do is use variable-length array declarations and alloca() in the same function. As VLA variables have scope life-time and allocas have function life-time they are not compatible — and allocations could overlap. Yet, not all compilers (/versions) that support both warn when they are used together.
cksk 1 days ago [-]
The array at the end of the struct is not a VLA on the stack. When you allocate the struct, you add enough extra to hold the contents of the array. VLAs on the stack are pretty much universally considered a bad idea, as far as I can tell.
Yes, attacker controlled size without limit is bad (and this is also true for heap allocations). For VLAs there is -Wvla-larger-than that can be used to ensure there is a hard limit. To understand the risks of VLAs one also has to compare it to the alternatives. A fixed-size array on the stack is basically always worse. alloca is substantially worse. heap allocation may be a bit better, but also much slower.
1 days ago [-]
Pay08 2 days ago [-]
Is there some sort of tool that checks headers for this stuff? On the occasion that I write a C library, I prefer it to be directly usable in C++.
uecker 2 days ago [-]
You can just run it through a compiler in c++ language mode.
nulltrace 20 hours ago [-]
Compiler mode won't catch the `extern "C"` thing though. Both sides compile happily, link blows up on mangled names. What I do is just keep a throwaway .cpp in tests that #includes the header and calls a few of the public functions. Dumb but it's basically the only thing that ever catches that case before some downstream user does.
taylodl 1 days ago [-]
[dead]
grougnax 2 days ago [-]
[flagged]
grougnax 2 days ago [-]
[flagged]
varispeed 1 days ago [-]
Man this table is disturbing. "Same", same as?
sylware 1 days ago [-]
Please stop using c++. It has a way too complex syntax creating a hard dependency on beyond reasonable "real-life" compilers and runtimes.
Many of these differences are intentional and defensible from the C++ side. But some are still surprising because they invalidate patterns that were historically common, performant, or idiomatic in C.
The interesting part to me isn’t "C vs C++," but where the languages diverged philosophically: object lifetime vs raw storage, stronger type systems, implicit conversions, ABI and optimization assumptions, and the boundary between "portable" and "works on my compiler."
I’d also be curious which C constructs people still genuinely miss in modern C++. For me, restrict is still near the top of the list.
Also, let's not forget that implicit casts between unrelated pointer types is only a warning in C. Fortunately, modern C compilers started treating it as an error by default because it caused so much harm: https://gcc.gnu.org/gcc-14/porting_to.html. In C++ this was always a compiler error.
A warning in C has the meaning of a "stern warning" aka. "That very much won't work, I warned you!". An error means, "I literally, don't what you mean".
Also as far as I know, the C standard only talks about diagnostics.
I’m not arguing that that’s better, or worse, but it’s definitely true and by no means a myth.
I.e. most of the time the typing in real C++ code isn't meaningfully stronger than that found in C code.
...so much this! A void pointer is an "any-pointer" by design. It shouldn't require casting from and to specific pointer types, that defeats the whole point of having void pointers in the first place.
You don't need to explicitly cast T* to void* (guaranteed to be safe), you only need to cast when converting out of void*.
The rules are basically the same as casting between pointer-to-derived-class and pointer-to-base-class and they make sense.
Yes, downcasting can be unsafe and should be used carefully, but what's the alternative? At least in C++ you can't cast between unrelated types without an explicit reinterpret_cast (or C-style cast).
It is also not clear what is gained by forcing programmers to add a cast. Void pointers should be used sparingly anyway.
If C++ programmers do not use modern safety features, that's really their fault. C-style pointer casts should be flagged in code review.
> but that it becomes even less safe by adding a C-style cast.
At least in C++ there is a safer option. If you really want to be on the safe side, you can even to a dynamic_cast (assuming your code base allows RTTI).
> It is also not clear what is gained by forcing programmers to add a cast.
I think the point is to make it explicit and stand out.
Another annoying detail is that C++ doesn't seem to like forward references of `enum`s. That is, while
is fine in both C and C++ even before `struct A` has been defined, apparently is not cool in C++ until after `enum A` has been defined.One arguable benefit of keeping your C code compatible with (or at least convertible to) C++, is that you can theoretically use scpptool's auto-translation feature as build step to produce memory-safe executables from C code via transpilation to a memory-safe subset of C++.
[1] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...
I should add here that there's also (3): Switch to Fortran, which made fundamentally different choices and is IMO the only fully supported higher-than-C level language that can produce HPC applications without fighting a compiler left and right.
There are some ATLAS TDAQ/HLT papers with my name on them.
Template metaprogramming, multi-threading, and custom IP protocols where much more relevant.
* built-in multidimensional arrays with efficient storage.
* related to this: built-in array intrinsics
```
real, dimension(100,100) :: A, B, C
C = A + B
```
this kind of code is already a close-to optimal "naive" implementation (not considering parallelization). so you start already at a solid place. then you can easily run it in parallel without too much specialized knowledge with OpenMP, OpenACC, MPI or even CUDA. the only thing you really need to be aware of when implementing your own loops/kernels: the intrinsic storage order, to optimize for cache hits.
* crucial: all the above amounts to a standard/best practice about how data is structured and formatted. everyone just uses the built-ins. Thus, interoperability between native Fortran numerical libraries is usually a complete non-issue. Meanwhile, Cpp has a fractured ecosystem with different array/vector types for its libraries. Converting between one and the other is usually a no-go.
* next, the intent plus pass-by-reference system. it combines IMO the best of both worlds of a functional vs. procedural approach:
* finally, a clean symbol definition system that decouples types from byte lengths. a `float` in fortran is just `real(4)`, a double is `real(8)`, a long int is `integer(8)` and so on. now, it's trivial to do a bit of preprocessing to switch the precision.However, the last part is where Cpp has a strong advantage: Well supported meta-programming (generics, templating or even just well supported pre-processors). Fortran's compilers come with a lot of built-ins, so the lack of these is less of an issue than you might think, but it's still a limiting factor. All that being said, a typical scientist doesn't tend to care and just wants to solve a particular problem rather than thinking in generalized frameworks - and that's why I find Fortran still serves them better for numerics than anything that came since.
[1] https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3734.pdf
C++ is 1990's Typescript for C++, while C folks still think is a portable Assembly instead of designed to an abstract machine model.
As such C++ community embraces high level abstractions and type systems improvements, whereas C wants to still code as targeting classical hardware.
> C [community] wants to still code
> many still don't know to distinguish
> the culture that... despite easy proof that isn't the case
> devs wrongly assume
> self inflicted complexity
> considered an advantage when argued by C folks
> when the same crowd points
> as the C crowd pretends it to be
You're arguing in this thread not by addressing what people are actually saying but by bringing up some hypothetical version of what "the C Community" thinks, then arguing with that.
Also it isn't a C invention to have the compiler dump the Assembly output instead of object code.
Now the culture that C language constructs in 2026 are still 1:1 to Assembly instructions, that pretty much prevails, despite easy proof that isn't the case at various compiler optimization levels.
Proficient devs, well many still don't know to distinguish what is their compiler, and what ISO says.
So it's all about understanding and control, not about some idea that C was defined in terms of assembly instructions, which it obviously is not. That's a total strawman.
Then get surprised when it doesn't map to the SIMD/SIMT NUMA machine their code actually executes on.
I am talking about self-inflicted complexity that is entirely within the C(++) machine model. Avoid that complexity and you're pretty good already. Only drop down to concrete hardware arch level where it makes sense. But largely, the C machine model is still very much suited as a model for actual hardware. Writing straightforward obvious code allows you to stay in control of memory layout and the data transformation paths. It easily gets you within <<2x of what you could achieve with hand coded assembler for the >90% of the code that are pretty boring and straightforward. And obviously you couldn't get the work done in time when coding everything in assembler.
I have seen plenty of self inflicted complexity in C, starting in the golden age of Yourdon Structured Method, and all those libraries that replicate C++ basic features with preprocessor macros.
https://replicated.wiki/blog/abc
Also when we eventually start talking to agents that perform the whole execution steps by themselves, that is kind of irrelevant.
Except for the lucky ones that still code to keep the infrastructure going, which is mostly C++.
If C would be so hardwired to the PDP-11 architecture it would have died with it. In reality C works just fine on all sorts of hardware (like GPUs) with only minor extensions.
I am also tired that language extensions in C to work around ISO defencies is considered an advantage when argued by C folks, while at the same time it is considered a language design fault when the same crowd points to other programming languages.
The PDP-11 had both 8 and 9-bit bytes. Thats a complexity that few programmers have to touch on, today.
Basic stuff like SIMD, SIMT, without requiring users to go beyond language extensions, something that any programming language can offer in similar capacity?
> something that any programming language can offer in similar capacity?
By your measure a lot of other languages don't offer anything to begin with, because they do not have a standard at all, only a reference implementation.
...I've seen this more often in the opposite direction. Since C++ is stuck with a ca 1995 non-standard subset of C, C++ coders usually have a very outdated view of C.
> I’d also be curious which C constructs people still genuinely miss in modern C++.
Not implementing the full C99 designated init feature set was a huge missed opportunity in C++20. Every single feature of C99 designated init is important and clicks with the other features and the rest of the language, take one or two away and it becomes mostly useless (e.g. the order requirement in C++20 means that designated init is only useful for trvial structs).
It's especially tragic because Clang already had the full C99 designated init feature set in C++ mode implemented long before C++20 and it worked just fine.
> The interesting part to me isn’t "C vs C++," but where the languages diverged philosophically
IMHO this "schism" was completely unnecessary and only happened because of ignorance and hubris by the C++ designers. Objective-C shows that C can be extended with radical new features but without messing up the "C side" (e.g. ObjC features don't overlap with C features, which means that ObjC is automatically compatible with the latest C standards).
In the end it's not a big deal of course, C and C++ are now entirely different languages and longterm that's for the better. Even the C++ peeps seem to have come to that realization and no longer recommend to "compile C in C++ mode" (like Herb Sutter in 2012 when trying to justify why MSVC had no C99 support: https://herbsutter.com/2012/05/03/reader-qa-what-about-vc-an...):
This was bad advice back then and is even worse advice today. At least MSVC got "good enough" C99 support a couple of years later (in VS2015), but after a few hopeful years after 2019 it looks like MSVC development has completely stalled again.Having attempted to implement it correctly in slimcc, there are indeed some edge cases[1][2] that justify not adapting it fully.
[1] Unordered side effects; evaluation of expressions with overlapped destination is implementation defined but not listed as such (the wording in standard is "can potentially not be evaluated").
[2] Both GCC and Clang still get this wrong in 2026: https://github.com/llvm/llvm-project/issues/190858
Following on the Secure Future Initiative activities.
The C updates have been what is required to compile critical FOSS projects, or support big name customers on Windows.
Apple and Google are also not racing to adopt new clang versions on their platforms.
The languages have diverged a lot, it's true. Still, it is worth noting that all the code in TCPL 2nd Ed was compiled with Stroustrups C++ compiler, as there wasn't a C compiler available. Source: Preface/Acknowledgments.
How did Clang handle differences between member declaration order and the order in which initializers appeared?
https://www.godbolt.org/z/ex138rh51
(the warnings in C++ mode had only been added after C++20)
My pet peeve with C++ is that the sequence point operator can be overloaded at which point it stops being a sequence point.
https://gitlab.com/libeigen/eigen/blob/master/Eigen/src/Core...
Edit: I should've had more conviction in my instincts, this is slop.
Curiously my comment above was on +3 karma last time I looked, but now it's on -2. It seems like the median HN user is getting worse at slop detection (or is otherwise ambivalent towards slop comments).
Perhaps be more careful in trying to make LLM output look like you wrote it yourself. The incongruent punctuation mark types, with curly apostrophes and straight double quotes mixed together in the same text, are a dead giveaway.
- C `_Atomic(T)` and C++ `std::atomic<T>`. C++23 has C compatible header `stdatomic.h` that defines `_Atomic(T)`, but it's still problematic
- C `_Noreturn/noreturn` and C++ `[[noreturn]]`. C23 `[[noreturn]]` makes them compatible
- C inline and C++ inline are different. Good news is their `static inline` are the same
- C has anonymous struct. C++ doesn't. Both have anonymous union though
If you're allocating something on the heap anyway, you shouldn't be forced to pay for an indirection in order to have some variable-sized data in the object, you should just be able to put it all in the one allocation. (Sure, you can achieve that with placement new hackery but that certainly isn't "idiomatic" C++.)
Of course that's completely incompatible with the way allocation and construction work (storage has to be allocated before the constructor runs). Hence "design flaw" rather than "missing feature."
I use this approach as part my zero-copy serialization library for what I call "out-of-line" sequences.
It does require smart usage of std::launder to be standards-compliant though.
However C++ had at time no default initialization for unmentioned fields, so in 2017 I had to remove it when converting the code to C++
https://godbolt.org/z/3aKaa7dnM
only if you specifically ask to get an error, would you actually get it.
That is for when the owner is a std::string or an owning range respectively. But a raw pointer does still make sense as a non-owning view over a single element, doesn't it? I'm new to C++ so I might be wrong.
Instead of letting compiler implementers decide which features to add and how to implement them, C++ employs a standards-first, top-down approach. Features are often defined by committee members who may not use modern C++ in their daily workflows, leaving it entirely up to the individual implementations to catch up.
Some features were standardized back in 2023, yet not a single implementation supports them in 2026.
https://en.cppreference.com/cpp/compiler_support
https://cppstat.dev/
I'm sure I could argue with you about the actual technical differences but this part in particular is very, very stupid.
JTC1/SC22 † shouldn't exist at all. A committee structure is a bad way to do this work, and the practice of having periodic meetings - exclusively in person for much of the time these existed - actually makes it less rather than more useful.
ISO mandates a bunch of rules and procedures which surely make some sense if you're agreeing on thread dimensions for oil pipelines but are completely inappropriate for this work and yet because they're ISO committees the WG14 and WG21 processes are captured.
I don't think it makes good sense to use an SDO for this work, but if you must have an SDO for some reason beyond ego then you could do a lot better than ISO. Check out TC39 for example.
† The C and C++ standards committees are respectively Working Groups 14 and 21 of the Sub-committee on Programming Languages, SC22, of the (First and only) Joint Technical Committee between ISO and the IEC. Yes it's committees all the way down. "This programming language standard could have been an email".
ISO agreed that despite there being an existing, popular, broadly supported and open XML document standard they should define Microsoft's proprietary alternative OOXML as an international "standard". They even held votes repeatedly until the voters gave the "correct" answer... no worry about "industry donations" there.
Maybe I'm missing something though and there are other alternatives done differently in other languages?
I also think that where you want monomophization, macro seem fine. I do not think this necessarily has to be clunky, but this is just a guess.
If just "providing my own" would help why wouldn't the stdlib benefit too? You're going to have to spell out what you think can actually work here if you want me to believe there's "no problem".
Using C macros to replicate Rust's monomorphism has several drawbacks: they are inherently unhygienic, even in comparison to Rust's own; you can't set type-bounds; they aren't even a part of C proper, etc.
I prefer Rust's approach with the choice between generics, macros, dyn and Any.
C macros are certainly a proper part of C and one can also certainly add type-bounds. But yes, they are not ideal. Still, if one wanted to do this, one could certainly improve them a lot for type-generic programming. I would prefer this to having macros, generics, a const expression sublanguage, and vtables.
Specialization provides more opportunities for optimizations, whereas proper type-erasure (which bars specialization optimizations) doesn't due to lack of type information.
"C macros" are a part of the preprocessor, which runs before the actual C compiler. As such, it lacks all semantical information the C compiler would have at that point, such as function implementions. In practice, macros in C serve two purposes: manipulating C source code (which Rust macros can also do, but with more hygiene); specialization polymorphism, but worse (in which both Rust's generics and C++ templates do better).
Type-erasure can be undone by specialization. In contrast, monomorphization specializes the code first, at which point it becomes much more difficult to unify it again.
Sorry, my comment about preference was badly phrased: What I meant is that I prefer to make macros better, than having all the different but partially overlapping techniques in the same language. This is the complexity I am complaining about in both C++ and Rust.
I haven’t used rust, but having gotten used to C and C++ at a time, I expect that would happen with rust, too, if I started using it. Because of that, I think this is just a matter of familiarity.
> polymorphism based on monomorphization
Implementation detail (yes, there is only one implementation at the moment, but this means it can be changed without changing the language)
> the requirement for many dependencies to get anything done
Fixable if a party is willing to write or package together a fairly large set of dependencies into a single package.
> an ecosystem susceptible to supply-chain attacks
Fixable if that party is trustworthy. Also, for which languages is this less of a problem? You either have third-party libraries and a potential security problem, or you don’t, and need to write more code.
See Ada, D, Delphi,....
Granted, priorities, one cannot fix everything at the same time.
Loosely coupling a language to its compiler is 20th century thinking for when programming languages were simple. It works for C because C is simple enough to be implemented over and over again. But for today's hyper complicated languages, multiple implementations is a pain for everyone.
When a programming language community fractures into multiple incompatible implementations everyone is worse off.
Are you sure about this one? I don’t know exactly who’s in the committee these days but last I checked they were all hardcore C++ programmers with decades of experience from the trenches.
Now, I rather have them than not, but it is still clunky.
https://www.open-std.org/Jtc1/sc22/wg21/docs/papers/2003/n14...
Eh, all of the committee members I've known are obsessed with modern C++, and "can this feature be implemented?" is definitely a blocker; numerous features got kicked down the road from C++0x to later versions because compilers weren't ready for them.
Rust is a language which isn't backwards-compatible, and certainly not compatible with source code in other languages.
Now, sure, Rust has its advantages, but - how can you fault C++ in the context of compatibility?
for example you want to add nice feature to c++ with nice syntax, but there is a similar syntax somewhere in C that nobody uses, but you have to support it. you end up with nice feature with horrible syntax.
For example in Rust there is one big entity that currently pours a lot of energy into improving C++ interop. Now, this is not exactly a niche topic, but especially in a world where AI makes many rewrites possible that we wouldn't have daunted to think about a couple of years before, we shouldn't waste too much effort to save legacy companies enormous codebases at the detriment of our preferred language.
Another well-known counterexample is implicit conversion from void*. In C89 you can do `int* foo = malloc(100);` but in C++ it requires an explicit cast from void* to int*.
I don't believe there was ever a time, even pre-standardization, when C++ was a strict superset of C; it always had little incompatibilities here and there.
It looks like you're right and the answer to when was C++ a superset of C may well be "never".
From the description, Cfront had always been a full-fledged parser that only happened to output C since the very beginning.
perhaps more accurately a fully fledged compiler (that emitted C)
?: has another execution priority.
Implicit cast scenarios are reduced in C++.
It doesn't matter unless you are using constructors or modifying some variables in the initialization expression anyway.
This takes the cake.
Compilers can often do some static bounds-checking of such arrays. But because those features had been introduced together with variable-length array stack variables , people have lumped them all together and thus shunned these features that could otherwise have added safety.
BTW, one thing you should never do is use variable-length array declarations and alloca() in the same function. As VLA variables have scope life-time and allocas have function life-time they are not compatible — and allocations could overlap. Yet, not all compilers (/versions) that support both warn when they are used together.