Although there are C features I like that I would need to remove before introducing C++ to a codebase, so this may not always be so simple.
Although there are C features I like that I would need to remove before introducing C++ to a codebase, so this may not always be so simple.
What is mess in C++? Yes, it has some shady parts and is more complex than C, but this complexity provides expressiveness and type safety. And you can always use only features from C++ you find useful.
> also contribute to projects that introduce C++ into a C code base, and I really wish they hadn't done this.
What is problematic in these projects other than unfamiliarity of C++ for developers previously used only C?
Just a couple examples:
How do you make a shallow copy of an object in C++, how do you do the same thing in C? Knowing I can just memcpy _any_ struct in C and have a valid shallow copy is pretty huge.
How do you create a stable ABI in C++? How do you make one in C? In C, knowing that all my exported functions will mostly just work and be stable as long as I don’t change the interface is also huge and allows you to create cross language bindings pretty trivially.
Shallow copies aren't generally possible for classes storing something indirectly, since it violates ownership semantics. That's not how things are done in C++.. Operator = is usually used for making copies, which is optimized to memcpy for POD structures, but for something more complex may perform extra work for doing an actual deep copy.
> How do you create a stable ABI in C++
It's a complex topic. Basic ABI for calling functions is identical to C. But one need to keep in mind, that type layouts and internal implementations of library types (like containers) may differ from implementation to implementation and from version to version.
> knowing that all my exported functions will mostly just work and be stable as long as I don’t change the interface
Nothing prevents you doing this in C++. You can have C-style external interface and use all goods of C++ internally.
"That's not how things are done in C++" I like C, because I can just decide for myself how things are done here.
You left out the part of my comment where I said “pretty trivially” lol. C++ is mostly a superset of C, which is why I’m not arguing that it’s impossible to do these things in C++. I like C++ too, and think some of the things in there are great. But the simplicity of C is nice. I would often get overwhelmed thinking about all the different constructors in C++ and what I needed to do to ensure that I didn’t accidentally tank performance by copying objects around everywhere (and potentially calling some invisible constructor that did extra work in the copy function). In C, I know when I memcpy it’s just gonna move some bytes around.
Overall, I really don’t care all that much one way or the other. But I can see how people could prefer C to C++. I could also see how people prefer C++. I really really miss function overloading and <string.h> when I use C
Ah, the old "programmers just need to be more disciplined when using C++".
Yeah, C programmers have never seen that argument before...
A difference between C++ programmers and C programmers is that C++ programmers have the awareness to know their language has bad parts to avoid, but C programmers are oblivious to the bad parts until someone uses it, but otherwise sing paeans for the language.
Nitpick: that isn’t possible anymore. gets was removed from C in C11 (https://en.cppreference.com/c/io/gets)
I do not think C++ is more expressive or type safe.
$ echo -ne "#include <stdlib.h>\nint foo() { return malloc(42); }" | gcc -xc -fsyntax-only -c -
<stdin>: In function ‘foo’:
<stdin>:2:20: warning: returning ‘void *’ from a function with return type ‘int’ makes integer from pointer without a cast [-Wint-conversion]
Whereas in C++ it's a hard error: $ echo -ne "#include <stdlib.h>\nint foo() { return malloc(42); }" | gcc -xc++ -fsyntax-only -c -
<stdin>: In function ‘int foo()’:
<stdin>:2:26: error: invalid conversion from ‘void*’ to ‘int’ [-fpermissive]
Does this not count as "more type safe" to you?I do want an error when I violate types, I do not want it for void, because that's the whole meaning of void. C++ manages to make it the worst of both worlds.
I’m mostly happy with implicit casts from void* to int* or something. But in this code example, we see an implicit cast from void* to int, casting the pointer itself into a number that might not even fit the pointer. This is rarely what you want. I’d much rather explicit casts in this case.
My understanding is that C has a different understanding of the word warning than other languages. Such as in "stern warning" or "warning: the building will collapse if you take that brick out".
This is a philosophical problem. C has false positives to the question: "Is that a program", and some people find that to be idiotic (I don't, this is why I like C). But this becomes really logical, when you have in mind, that the point is that you do not want to have false negatives.
> Otherwise, if a program contains a violation of any diagnosable rule or an occurrence of a construct described in this document as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.
The C++ standard does seem to explicitly require the compiler to reject translation units with a failed static_assert. (The requirements for #error are a little confusing; not sure if a failed #error in a conditional block requires failure.) But I couldn't find a rule that mandates failure for invalid implicit int conversions. Implicit pointer to int conversions are invalid for the same reasons in both C and C++. Basically, AFAICT C++'s type safety in this regard is a matter of historical compiler behavior, and now the major C compilers are adopting that behavior, which they previously had allowed (with a mandatory diagnostic message) for backward compatibility reasons.
Also, a question for a C expert like you, uecker: In your opinion, does the compound literals feature have complex lifetimes rules or complex semantic rules otherwise?
I am not sure I understand the second question. I do not think compound literals have complex lifetime rules. The lifetime end with the corresponding block, as do other objects in C (one needs to know what a block is, some people confuse this with scope). There is some potential issue which may be surprising when passing them to macros that make use of the statement expression extension, as the lifetime is then limited to the macro body.
I have read up on it, and I do not remember why I arrived at the conclusion that compound literals have complex rules. Their rules are different from C++ and Rust temporaries, but the rules of compound literals are not complex, though their block lifetimes do have to be understood. I have seen at least two C developers being confused about their lifetimes (mostly about when used inside if-statement's condition or in a loop), but most C developers seem to grasp compound literals and their lifetime quickly, and the cppreference.com page is relatively short, which reflects the simplicity of compound literals. It helps that one can understand them as a local variable, local to the specific block that they are used in.
In contrast, C++ and Rust temporary lifetimes can vary, and those lifetime rules can confuse developers, like in https://fasterthanli.me/articles/a-rust-match-made-in-hell .
Mojo seems to avoid many of C++'s and Rust's issues on the topic about lifetimes.
Edit: The trade-off of C compound literals is as far as I can tell that their lifetime is longer than sometimes necessary.