Takeaway #1: "C and C++ are different: don’t mix them, and don’t mix them up"
Where "mixing C/C++" is helpful:
- I "mix C in with my C++" projects because "sqlite3.c" and ffmpeg source code is written C. C++ was designed to interoperate with C code. C++ code can seamlessly add #include "sqlite3.h" unchanged.
- For my own code, I take advantage of "C++ being _mostly_ a superset of C" such as using old-style C printf in C++ instead of newer C++ cout.
Where the "C is a totally different language from C++" perspective is helpful:
- knowing that compilers can compile code in "C" or "C++" mode which has ramifications for name mangling which leads to "LINK unresolved symbol" errors.
- knowing that C99 C23 has many exceptions to "C++ is a superset of C" : https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B...
The C library headers for libraries I write often include C11/C99 stuff that is invalid in C++.
Even when they are in C89, they are often incorrect to include without the include being in an `extern "C"`.
The modern fmt-inspired std::print and std::println etc. are much nicer, preserving all the type checking but losing terrible ideas like stored format state, and localisation by default. The biggest problem is that today C++ doesn't have a way to implement this for your own types easily, Barry illustrates a comfortable way this could work in C++ 26 via reflection which on that issue closes the gap with Rust's #[derive(Debug)].
It takes a little bit of an effort to make a header work on C and C++. A lot less effort than making a single Python file work with Python 2 and 3.
Almost seamlessly. You have to do
extern “C” {
#include "sqlite3.h"
}
(https://isocpp.org/wiki/faq/mixing-c-and-cpp#include-c-hdrs-...)cin >> a;
Then the program goes berserk as soon as the first non-number is read out of standard input. All the other "cin >> integer" lines are immediately skipped.
Yes, I know about error checking, clearing error condition, discarding characters. But it's a whole lot of stuff you need to do after every single "cin>>" line. It makes the simplicity of cin not worth it.
I’m not sure about the stdlib version, but with fmtlib you can easily implement formatters for your own types. https://fmt.dev/11.0/api/#formatting-user-defined-types
You check error for the whole batch.
* Performance
* Support for localization (as the format string and positions of values to format differ between languages).
* Code reuse & dogfooding - the data structures used in iostreams are not used elsewhere, and vice-versa
* C and OS interoperability - as you can't wrap a stream around a FILE* / file descritor
* bunch of other stuff...
iostreams work, but are rather crappy.
The less C the merrier.
If you care about correct use of localisation, standard C and C++ libraries aren't really what you're looking for, or even C and C++ to start with.
Hindsight is 20/20, remember that. Streams are not that bad of an idea and have been working fine for decades. You haven't named a problem with it other than the fact the operators are used for other stuff in other contexts. But operator overloading is a feature of C++ so most operators, even the comma operator, can be something other than what you expect.
>The biggest problem is that today C++ doesn't have a way to implement this for your own types easily, Barry illustrates a comfortable way this could work in C++ 26 via reflection which on that issue closes the gap with Rust's #[derive(Debug)].
You can trivially implement input and output for your own types with streams.
You appear to be a Rust guy whose motive is to throw shade on C++ for things that are utterly banal and subjective issues.
The only reason why iostreams are slow is because of its incompatible buffering scheme, and the fact that C and C++ need to stay in sync when linked together. And that brand of slow is still faster than other languages, except sometimes those that delegate i/o to pure C implementations.
I agree. It's lunacy. just be explicit and use functions or equivalent like literally every other language.
> If you care about correct use of localisation, standard C and C++ libraries aren't really what you're looking for, or even C and C++ to start with.
What do you recommend instead? fscanf (STDIN, "%d", &a);
the program goes beserk as soon as the first non-number is read out of standard input.in both cases, you need error checking (which you "know about").
struct Foo {
int a;
float b;
std::string c;
};
Foo foo;
std::cout << foo;
with no extra code. It's called reflection, where the compiler can generate good-enough code to generate a character-stream serialization of an object without any human intervention.I believe Rust has adopted similar idioms. I’ve heard the overall idea referred to as Railway-oriented programming.
In C++ you could implement it with exceptions, though they bring in a bunch of their own baggage that you don’t have to deal with when using monads.
This disn't stop with <iostream>, they keep doing it - the latest example I can think of is std::ranges operations being "piped" with |.
Many C++ coders are oblivious to those differences (myself included before I switched from 'mainly C++' to 'mainly C') because they think that the C subset of C++ is compatible with 'proper' C, but any C code that compiles both in a C++ and C compiler is actually also a (heavily outdated) subset of the C language (so for a C coder it takes extra effort to write C++ compatible C code, and it's not great because it's a throwback to the mid-90s, C++ compatible C is potentially less safe and harder to maintain).
For instance in C++ it's illegal to take the address of an 'adhoc-constructed' function argument, like:
sum(&(bla_t){ .a = 1, .b = 2, .c = 3, .d = 4 });
(godbolt: https://www.godbolt.org/z/r7r5rPc6K)Interestingly, Objective-C leaves its C subset alone, so it is always automatically compatible with the latest C features without requiring a new 'ObjC standard'.
The pre-processor original compiler, before the GCC fork, would leave everything else alone, blindly copying into the generated C file.
It's more code, sure, but it buys you a lot of good things. I/O is hard.
I think this is a very lazy and somewhat conspiratorial take.
C++'s IO stream library, along with C++'s adoption of std::string, is a response to and improvement over C's standard library support for IO. That alone makes it an invaluable improvement. It's easy and very lazy to look back 30 years ago and badmouth things done back then.
It's also easy to complain about no one proposing changes when literally anyone, including you, can propose changes. The only need to do the legwork and put their money where their mouth is. The funny part is that we see frameworks putting together their own IO infrastructure and it ends up being not good, such as Qt's take on IO.
But talk is cheap and badmouthing doesn't require a pull request.
cin >> a;
and assume that it works.But also ...
sscanf (the_buffer, "%d", &a);
doesn't help the problem in any substantive way.https://github.com/fmtlib/fmt is what it's based on, for C++11 and up support.