Remix.run Logo
butterisgood 5 days ago

A pet peeve of mine is when people claim C++ is a superset of C. It really isn't. There's a lot of little nuanced differences that can bite you.

Ignore the fact that having more keywords in C++ precludes the legality of some C code being C++. (`int class;`)

void * implicit casting in C just works, but in C++ it must be an explicit cast (which is kind of funny considering all the confusing implicit behavior in C++).

C++20 does have C11's designated initialization now, which helps in some cases, but that was a pain for a long time.

enums and conversion between integers is very strict in C++.

`char * message = "Hello"` is valid C but not C++ (since you cannot mutate the pointed to string, it must be `const` in C++)

C99 introduced variadic macros that didn't become standard C++ until 2011.

C doesn't allow for empty structs. You can do it in C++, but sizeof(EmptyStruct) is 1. And if C lets you get away with it in some compilers, I'll bet it's 0.

Anyway, all of these things and likely more can ruin your party if you think you're going to compile C code with a C++ compiler.

Also don't forget if you want code to be C callable in C++ you have to use `extern "C"` wrappers.

TuxSH 5 days ago | parent | next [-]

> It really isn't. There's a lot of little nuanced differences that can bite you.

These are mostly inconsequential when using code other people write. It is trivial to mix C and C++ object files, and where the differences (in headers) do matter, they can be ifdefed away.

> void * implicit casting in C just works, but in C++ it must be an explicit cast (which is kind of funny considering all the confusing implicit behavior in C++).

This makes sense because void* -> T* is a downcast. I find the C behavior worse.

> enums and conversion between integers is very strict in C++.

As it should, but unscoped enums are promoted to integers the same way they are in C

> `char * message = "Hello"` is valid C but not C++

Code smell anyway, you can and should use char[] in both languages

You didn't mention the difference in inline semantics which IMO has more impact than what you cited

account42 4 days ago | parent | next [-]

> you can and should use char[] in both languages

Not for temporaries initialized from a string constant. That would create a new array on the stack which is rarely what you want.

And for globals this would preclude the the data backing your string from being shared with other instances of the same string (suffix) unless you use non-standard compiler options, which is again undesirable.

In modern C++ you probably want to convert to a string_view asap (ideally using the sv literal suffix) but that has problems with C interoperability.

TuxSH 4 days ago | parent [-]

Right, I've checked that char foo = "bar"; is indeed the same as the const char variant (both reference a string literal in rodata), which IMO makes it worse.

About string literals, the C23 standard states:

    It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.
therefore `char foo = "bar";` is very bad practice (compared to using const char).

I assumed you wanted a mutable array of char initializable from a string literal, which is provided by std::string and char[] (depending on usecase).

> In modern C++ you probably want to convert to a string_view asap (ideally using the sv literal suffix)

I know as much

butterisgood 4 days ago | parent | prev [-]

I was not trying to be exhaustive.

And I think you're downplaying many of the ones I mentioned, but I think this level of "importance" is subjective to the task at hand and one's level of frustrations.

babel_ 4 days ago | parent | prev | next [-]

The two will also continue to diverge over time, after all, C2y should have the defer feature, which C++ will likely never add. Even if we used polyfills to let C++ compilers support it, the performance characteristics could be quite different; if we compare a polyfill (as suggested in either N3488 or N3434) to a defer feature, C++ would be in for a nasty shock as the "zero cost abstractions" language, compared to how GCC does the trivial re-ordering and inlining even at -O1, as quickly tested here: https://godbolt.org/z/qoh861Gch

I used the [[gnu::cleanup]] attribute macro (as in N3434) since it was simple and worked with the current default GCC on CE, but based on TS 25755 the implementation of defer and its optimisation should be almost trivial, and some compilers have already added it. Oh, and the polyfills don't support the braceless `defer free(p);` syntax for simple defer statements, so there goes the full compatibility story...

While there are existing areas where C diverged, as other features such as case ranges (N3370, and maybe N3601) are added that C++ does not have parity with, C++ will continue to drift further away from the "superset of C" claim some of the 'adherents' have clung to for so long. Of course, C has adopted features and syntax from C++ (C2y finally getting if-declarations via N3356 comes to mind), and some features are still likely to get C++ versions (labelled breaks come to mind, via N3355, and maybe N3474 or N3377, with C++ following via P3568), so the (in)compatibility story is simply going to continue getting more nuanced and complicated over time, and we should probably get this illusion of compatibility out of our collective culture sooner rather than later.

aw1621107 5 days ago | parent | prev | next [-]

> C++20 does have C11's designated initialization now, which helps in some cases, but that was a pain for a long time.

C++ designated initializers are slightly different in that the initialization order must match the declared member order. That is not required in C.

o11c 5 days ago | parent [-]

It also completely negates their utility, even though the exact "problem" they always bring up is already solved when you use normal constructors.

jcelerier 5 days ago | parent [-]

Not sure I understand, since they're available in c++ designated initializes are one of the features I use most, to the point of making custom structs to pass the arguments if a type cannot be changed to be an aggregate. It makes a huge positive difference in readability and has helped me solve many subtle bugs ; and not initializing things in order will throw a warning so you catch it immediately in your ide

o11c 4 days ago | parent [-]

The problem is that there are a lot of APIs (even system and system-ish ones) that don't want to specify the order of their fields (or outright differ between platforms). Or that can't use a meaningful order due to ABI compatibility, yet the caller wants to pass fields in a meaningful order.

jcelerier 4 days ago | parent [-]

platform APIs like this are likely much less less than 1% of the things I call in my codebases. The few files i have open right now have absolutely no such call.

jandrewrogers 5 days ago | parent | prev | next [-]

> You can do it in C++, but sizeof(EmptyStruct) is 1.

Unless you use the C++20 [[no_unique_address]] attribute, in which case it is 0 (if used correctly).

butterisgood 4 days ago | parent [-]

I completely forgot about that!

IAmBroom 4 days ago | parent | prev | next [-]

> A pet peeve of mine is when people claim C++ is a superset of C. It really isn't. There's a lot of little nuanced differences that can bite you.

> Ignore the fact that having more keywords in C++ precludes the legality of some C code being C++. (`int class;`)

Your very first example reverses the definitions of superset and subset. "C++ is a superset of C" implies that C++ will have at least as many, if not more, keywords than C.

Other examples make the same mistake.

lelanthran 4 days ago | parent | prev [-]

> void * implicit casting in C just works, but in C++ it must be an explicit cast

In C, casting a `void *` is a code smell, I feel.

Most confusing one is how the meaning of `const` differs between C and C++; I'm pretty certain the C `const` keyword is broken compared to `const` in C++.