You can do that in C++, but by wrapping your C++ in C, more or less. Rust might provide a compelling alternative here eventually, but it's still a young language.
> I concede that the extra language complexity of C++ over C could be a disadvantage, but in my mind that doesn't outweigh the benefits.
It's a problem, C++ is so insanely complex, full of magic which is hard to grasp and provides more ways to shoot yourself in the foot than to do it right (modern C++ seems better in some regards but still…). C is often chosen because people want sth. reasonable simple so I think a language fitting in between, being richer than C but still fairly comprehensible would be nice.
I have tested this a year ago with both clang and GCC, and the optimized, stripped down binaries were identical in size.
Firstly, apart from a simple call stack, C has no automatic resource management. There are no automatic destructors. Thus it is too easy to forget to free some memory or unlock a mutex or decrement a ref count or whatever, especially when many code paths are possible after a resource has been allocated. The other options are to use the "goto cleanup" or "goto err" idiom, which is still way more error prone than automatic destructors.
And secondly, despite being touted as so "efficient", C offers no way to write code that is both generic and free of runtime overhead, apart from preprocessor macros (which are filled with their own issues), or automatic code generation scripts. Both C++ and Rust have metaprogramming facilities which provide the ability to write generic code without sacrificing performance or maintainability.
I mean, with C++, the automatic deterministic allocation/destruction alone is worth using it over C. And all of the downsides (larger binaries, etc.) can be overcome without much effort. I concede that the extra language complexity of C++ over C could be a disadvantage, but in my mind that doesn't outweigh the benefits.