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
Address white_house{
.street = "1600 Pennsylvania Avenue NW",
.city = "Washington",
.state = "District of Columbia",
.zip = 20500,
};
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 thisHowever C++ had at time no default initialization for unmentioned fields, so in 2017 I had to remove it when converting the code to C++
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."
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.
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.
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.
Designated initializers is one area where C feels much more expressive than C++. And that feature has been standard since C99.
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.> restrict: a C promise, not a C++ contract
This takes the cake.
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.
Man this table is disturbing. "Same", same as?
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++.
Please stop using c++. It has a way too complex syntax creating a hard dependency on beyond reasonable "real-life" compilers and runtimes.
[dead]
[flagged]
[flagged]
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.