4 ms·
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 fac
by butterisgood 1y 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.
- jandrewrogers 1y ago> 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 1y agoI completely forgot about that!
- aw1621107 1y ago> 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 1y agoIt also completely negates their utility, even though the exact "problem" they always bring up is already solved when you use normal constructors.
- jcelerier 1y agoNot 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 1y agoThe 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 1y agoplatform 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.
- TuxSH 1y ago> 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 1y ago> 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 1y agoRight, 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
- 1y ago
- babel_ 1y agoThe 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 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.
- lelanthran 1y ago> 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++.
- 1718627440 1y agoYes, casting in general is a code smell. (Not as in never use it, but as in use it as less as possible of course.) The thing is, void was introduced exactly to represent this use-case without casting. The type to represent random amount of bytes, check which type and then cast is called char *. void * is for when you know exactly which type it has you just need to pass it through a generic interface, which isn't supposed to know or touch the type. The other use case is for when the object hasn't a type yet, like from malloc. Previously everything was typed char *, then void * was used to separate the cases: casting from char *, don't cast from void *. Now in C++ both are the same again.
- IAmBroom 1y 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;`) 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.