to be clear, i'm neither defending nor bashing either language. i use and like both as appropriate. and it's fine to disagree, btw. please do not read "good" or "bad" into my attempt to describe either.

but i will also emphatically reject your position: "Constructors, exceptions, and so on are as similarly visible at the source level as macro definitions"

no they are not. you can certainly see what the macro is doing - you see it's definition, not just it's existence. whereas in c++ you have to trust that language/compiler to:

- build a vtable (what exactly does this look like?)

- make copy ctors

- do exception handling.

- etc.

none of these are explicit. all of them are closed and opaque. you can't change their definition, nor add on to it.

at issue at hand is both "magic" and openness. c gives relatively few building blocks. they are simple (at least in concept). user libraries construct (or attempt to construct) more complex idioms using these building blocks. conversely c++ bakes complex features right into the language.

as you note, there are definitely forces that work against the naïve original nature of c. macros, setjmp, signal handling, instrumentation, hardening, .so resolution, compilers replacing primitive accesses, etc. but all of those apply equally to c and c++. they are also more an affect of the ABI and the platform/OS than either language. in short, those are complaints and complexities due to UNIX, POSIX, and other similar derived systems, not c or c++ the language itself.

c has relatively few abstractions: macros, functions, structured control flow, expressions, type definitions. all of these could be transformed into machine code by hand, for example in a toy implementation. sure a "good" compiler and optimizer will then mangle that into something potentially unrecognizable, but it will still nearly always work the way that the naïve understanding would. that's why when compilers do "weird" things with UB, it gets people riled up. it's NOT what we expect from c.

c++ on the other hand has, in the language itself, many more abstractions and they are all more complex. you aren't anywhere near the machine anymore and you must trust the language definition to understand what the end effect will be. how it accomplishes that? not your problem. this makes it squarely a high-level language, no different than java or python in that facet.

i explicitly reject your position that "that C is qualitatively [not] different from C++ from a WYSIWYG point of view, [either] theoretically [or] in practice."

to me, it absolutely is. it represents at lower level interface with the system and machine. c is somewhere between a high-level assembler and a mid-level language. c++ is truly high-level language. yes, compilers and os's come around and make things a little more interesting than the naïve view of c in rare cases . but c++? everything is complex - there is not even workable illusion of simplicity. to me this is unfortunate because, c++ is still burdened by visible verbosity, complexities, land-mines, and limitations due to the fact that it is probably not quite high-level enough.

this is all very long winded. you and many other readers might think i'm wrong. the reason i'm responding is not to be argumentative, but because it is that it's by no means a "settled" question and there are certainly also plenty of people that see it a very different way. which i think is fine.