21 ms·
Goodbye C++, Hello C
- ncmncm 5y agoJust sad. If the language is not doing a great deal of your work for you, then you are not using it right. Doing it right, things flow fast and work right the first time. So, you don't care that it takes 30 seconds to compile, because you coded all afternoon and then compiled and it worked right away, with no debugging needed. After that, coding C feels like a big PITA where you have to tell the compiler everything over and over again, and need to compile every few minutes just not to get too far into the weeds. Any big system has some objecty bits, but O-O is a technique, not a religion. Same for anything-oriented: functional, data-oriented, what-have-you. Master the tools, don't be mastered by them.
- syockit 5y agoThe compiler cannot catch logic or design errors. If you made a mistake when designing the algorithm and implemented it as it is, you'd need to debug anyway. Sometimes it's something as simple as missing a minus symbol somewhere in your equations. Working with Eigen was especially painful. All the template magic it uses means what would have taken 1 second to compile if used BLAS/LAPACK, now takes 10-20 seconds.
- jcelerier 5y ago> The compiler cannot catch logic or design errors. If you made a mistake when designing the algorithm and implemented it as it is, you'd need to debug anyway But that's the thing, it can. - you can specify compile-time preconditions and use the type system to ensure value are in range (which catches a ton of errors) - you can use constexpr to enforce no UB (UB in constexpr evaluation is a compile error) For instance, it's trivial to make a type-checked db id in c++ which will give you a hard error if you use the id pertaining to a given type, to another type. That's much harder to do in C (without implementing a custom code generator at least that would basically reimplement templates) and has saved me lots of very costly hours of debugging when I see the amount of time I got compile errors I got from that.
- patrick451 5y agoHow does the type system catch this bug double negate(double x){ return x; } ?
- jcelerier 5y agoI'm sure that in 1986, before standardizing ANSI C, someone victoriously typed the same thing in front of his tty in a discussion about using types in declarations and not just doing it à la K&R with just the function name being called cowboy-style. Just because a system does not catches all bugs does not make it useless - for me, even if C++'s type system had caught only one bug it'd be worth it (and in most of my code, which is mainly about fairly specific domain objects, the compiler saves me from myself pretty much daily). In any case, I'd likely have this somewhere constexpr double negate(double x) { return -x; } // example implementation for the sake of the comment, there are much more exhaustive libs out there constexpr bool sample(auto func) { bool ok = true; double x = -1000.; while(x < 1000.) { ok &= func(x); x += 1.; } return ok; } static_assert(sample([] (double x) { return negate(x) == -x; })); this way I'd get a compile error if my cat walks on my keyboard and inadvertently removes the - (also a relatively common occurence)
- patrick451 5y agoYou're effectively just changing where you write your unit tests. So it's hard to agree that the type system is saving your from anything, when it's really just writing tests.
- jcelerier 5y agoBut no one said that type systems disqualifies from writing tests (and C++'s improvements over C aren't only about the type system). It removes, however, the need for a lot of tests that are needed when using weaker type systems, mainly because you can make invalid data unrepresentable. To give a few examples: 1/ in C++ you can write a type positive_int such that invalid values (negative ints) are not representable at all - either the operation a - b gives a correct, positive result (3 - 2 == 1), or you get an error that you can process if for instance you do 2 - 3. You can also write things in a way that the user only has to provide things in a declarative way. 2/ I'm working on an API for defining dataflow nodes for multimedia applications which tries to leave zero margin for error : the user simply has to define the inputs and outputs like this, as types, which the C++ type machinery is able to digest and show to the user : https://github.com/jcelerier/score-simple-api-2/blob/main/SimpleApi2/Distortion.hpp https://github.com/jcelerier/score-simple-api-2/blob/main/Si... In contrast, in C, one has to cast things left and right because the only tool you have is void* : see for instance https://cycling74.com/sdk/max-sdk-7.3.3/html/chapter_msgattached.html#chapter_msgattached_attr_custom https://cycling74.com/sdk/max-sdk-7.3.3/html/chapter_msgatta... or https://cycling74.com/sdk/max-sdk-7.3.3/html/chapter_msp_advanced.html https://cycling74.com/sdk/max-sdk-7.3.3/html/chapter_msp_adv... in both cases we are defining a dynamic dataflow node with various inputs / outputs (for instance an audio input and output, plus a control), which is then displayed in an user interface (thus things have to be reflected in some way). But in the C version, the onus is on the programmer to : - cast the inputs / outputs to the correct types according to the object's specification when using them - cast the values contained in the inputs / outputs to the correct types - access the correct inputs / outputs in the arrays there is just so much more opportunity for error, which entirely disappears in C++ 3/ Another example that you can see in my code: the make_uuid function. Takes a UUID in string form and converts it to binary uint8_t[16] at compile time ; also checks that the UUID is a valid one. Every time I use this function, this is a test that I do not have to write - I know that all UUIDs in my system are valid, because I use an "uuid" type which can only be created this way by the user.
- patrick451 5y agoClearly, if you missed a minus sign somewhere, your type system is not powerful enough (>:<)
- thechao 5y agoI like to think of a compiler as a virtual machine whose instructions are the tokens in your code, and whose output is object code. Taken that way, you can for sure optimize the input program (your C++) to execute faster on the interpreter (the compiler). Understanding how the language (any language with such features) is implemented lets you stray away from the slow parts. For instance, in C, every function has a unique, global name. That means the implementation in the compiler can be a hash-table from string to implementation. C++ allows function overloading; now, our 'hash table' is a much more complicated thing: first, we must consider every function in the overload set no matter what; second, we must pick which function is "best" — usually through some complicated unification scheme. In all, this means that C function lookup is O(1) (probabilistic), whereas C++ function lookup is O(n) at best, but with unification it might be O(n^2) or O(n^3). I've seen slow compiling code with hundreds of identically named functions. Templates are slow for two reasons: (1) they get shared via header files, so they get reparsed over-and-over-and-over; and, (2) they're implemented with a stringy interpreter whose job is to instantiate copies of the code. This is distinctly slower than macro expansion; generally, macro instantiation is of code and so occurs in source files — only requiring a single parse; and, also, there's just string-replacement, not an interpreter. But ... here's the thing. You can make your C++ code compile fast by simply not using slow paths through the compiler.
- WalterBright 5y ago> I've seen slow compiling code with hundreds of identically named functions. I sometimes think I've seen everything programmers do, and then something like this pops up. > You can make your C++ code compile fast by simply not using slow paths through the compiler. D is fast to compile because it sidesteps or redesigns features that make for slow compilation.
- moonchild 5y agoD is horribly slow to compile template-heavy code for exactly the same reasons as c++. And it encourages lots of monomorphization in e.g. ranges. The enhanced introspection and reflection (and CTFE) capabilities coupled with mixins also encourage code that is very slow to compile.
- adamdusty 5y agoThe author lists GLFW, stb_image, and ImGui as dependencies for their renderer written in C. Those are the same exact dependencies you would use to write a renderer in C++. Maybe you would add glm because you don't want write matrix math operations, but the author will have to do that in C anyway. I don't see how dependencies are an advantage for C.
- acmj 5y agoYou can go down the route to say C++ is largely a superset of C. You can always write C++ in the C way, so you don't see simplicity is an advantage of C.
- bruce343434 5y ago> You can always write C++ in the C way Nope. They have grown too far apart. My C11 codebase simply does not compile when `mv *.c *.cpp && g++ main.cpp`.
- kaba0 5y agoThat’s why grandparent wrote “largely”
- bruce343434 5y agoTheir second statement is still simply wrong. C has some ways of doing things which are simply unacceptable in C++. In C, you don't cast mallocs. You can initialize structs like `={0}` or `={prop=val}`. There's more, but the point is: if you are writing C++, you can't write it in a "C way". It won't compile at some point.
- kaba0 5y agoI interpreted the “C way” as in avoiding RAI, classes, references, etc. Just writing functions with structs and pointers. Not as “syntactic C”
- 5y ago
- 1024core 5y agoI've always been impressed by Go's compile times. The first few times I thought I'd made a mistake, and it was doing nothing (the prompt came back so quickly). Now I know better :-)
- christophilus 5y agoThis is my favorite OCaml and Go feature, and one that I wish all other languages would adopt.
- _wldu 5y agoI used C++ for a good while (systems programming). I like it and still use it for some things. But when I started using Go, I used C++ less and less. Go was simple like C and almost as fast. There is a time and a place for all languages, but if I could only have one, I'd probably pick C++. It can do almost anything in whatever programming style you prefer. It really is the most generic language I've ever used. That's both its strength and weakness. Also, the fact that C++ is standardized (ISO) and not controlled by a company (Google, Microsoft, Apple) I feel it offers greater freedom. No vendor lock-in.
- tayistay 5y agoThe article makes me wonder: how much headroom do compilers of newer slow-to-compile languages such as Rust or Swift have for improving compile times?
- yoz-y 5y agoSwift can reduce the “max time to compile a complex expression” indefinitely to force programmers to write simpler-to-compile code. /s
- adrianN 5y agoNicholas Nethercote has a series of blog post on speeding up the Rust compiler. [1] There are (were?) also efforts going to to reduce the amount of LLVM work by shifting some of that work further up in the stack. [1] https://blog.mozilla.org/nnethercote/author/nnethercotemozillacom/ https://blog.mozilla.org/nnethercote/author/nnethercotemozil...
- the_duke 5y agoIn addition to the mentioned micro optimizations, I'll add some possibilities from the Rust angle. Rust has really impressive incremental compilation built in already. The first compile is annoyingly slow, but additional ones will be surprisingly fast, often finishing in a second or two , even in larger code bases. Especially if you split up your own code into multiple crates. This could further be significantly improved by a demonized compiler that keeps everything in memory. Additionally there could be a way to share compilation artifacts via the package registry, also making first time compiles fast. There is also an effort to use Cranelift as a codegen backend, which can be faster than llvm for debug builds, and has the potential for JIT compiling functions on first use, which would make debug builds a lot faster again as well. Zig also has some really cool plans in this domain. There is lots of headroom to make compiling these static languages faster, but compilers would often need to be (re)written from scratch with these compilation strategies in mind.
- maleldil 5y ago> Where C++ gives you many slightly different takes on the same concept (e.g. unique_ptr, shared_ptr and raw pointers), C gives you one way to go and that one has a conveniently compact notation (such as float). You use unique_ptr and shared_ptr because float is unsafe. If you don't care about the safety that smart pointers provide, you can use float* in C++ too. > ticking with the example of matrix math, it is baffling how C code is often more compact and readable than C++ code. A matrix declaration would be float matrix[4][4]; [...] Of course, you could take the same approach in C++ but then you are not really using C++. The example from above in C could be simply: float* top = matrix; But that's not that same thing, is it? I fail to see how you're actually getting the top 3 rows there; you're just assigning a pointer. Again, you're making your code less safe and expressive by using no abstraction at all. And please, how is `matrix_add(matrix_mul(A, B), c)` more compact and readable than `A*B + c`? And this is being kind to the C version. If the problem is with C++'s compilation times, then it's perfectly reasonable; this is one of the major problems of the language. But using unsafe constructs and then saying that they're better than C++'s safer alternatives is just comparing apples to oranges.
- jimbob45 5y agoI agree he used a trash example. I would offer include guards/pragma once as a slightly better example.
- badsectoracula 5y agoFWIW i've seen a lot of 3D codebases in C++ that avoid operator overloading and instead use something like `A.multiply(B).add(c)` to make more explicit what is going on. Some even break that into multiple calls instead of using a single expression.
- jolux 5y agoI fail to understand how that’s more explicit to be perfectly honest. If it’s hard to resolve where overloads are coming from then that’s a problem but otherwise it just seems more verbose for no benefit. Addition and multiplication are the operations in use here, why shouldn’t we use their operators to represent them?
- suby 5y ago>So what is the bare minimum? To use the GPU, I need a graphics API. Vulkan is the one that is most widely supported so that is a natural choice Vulkan can't possibly be the most widely supported graphics API can it? I have to imagine that targeting older hardware is better with OpenGL because it's been around forever, but I don't actually know and a quick google search doesn't turn up much.
- AussieWog93 5y agoYou're right. I did some graphics work in my last job. OpenGL2 is supported by just about every device made in the last 15 years, on every major operating system, including software OpenGL in VMs. Vulkan is not officially supported on OSX (although there is an unofficial port that is quite good, from what I understand) and only runs on modern hardware.
- badsectoracula 5y agoAnd by "modern" we really mean released the last few years. For example it doesn't run on my 2013 laptop (has a 660M GPU, Nvidia supports Vulkan only from 7xx series) or my (late) 2016 GPD Win (technically there is Vulkan 1.0 support under Linux but not under Windows and AFAIK even the Linux driver has issues).
- ninepoints 5y ago> Vulkan is not officially supported on OSX And neither is OpenGL
- Wowfunhappy 5y agoDeprecated, not unsupported. And, like, Apple ported OpenGL to Apple Silicon macs. I suspect it's going to stick around, it's just never, ever going to get updated.
- ninepoints 5y agoYou're kidding. The OpenGL implementation/support of Mac is pretty nonexistent/poor.
- winrid 5y agoIf you really like the simplicity of C I highly recommend Nim: * Compiles to C, so you stand on the shoulders of giants wrt compilers. * Very low overhead GC that doesn't "stop the world". I was working on a project recently where I had to just write a bunch of stuff to disk, and I tried Node, and then Java, and they were about the same, and I figured - yep, makes sense, it's IO bound. Nim got twice the throughput w/ an order of magnitude less memory usage! Because the serialization I suppose was the bottleneck and msgpack4nim is pretty fast.
- moonchild 5y agoIf serialization performance is an issue, then you may want to consider zero-overhead serialization tools like capn proto.
- winrid 5y agoYeah I know about Cap'n proto etc. Was just saying the tiny language can compete :) Language comparisons are almost always BS...
- jart 5y agoUse mmap and you won't need to write anything to disk; the operating system will do it for you. That's C's killer feature while also being the last thing on earth languages like Node and Java would ever permit developers to do.
- winrid 5y agoGood idea, will keep that in mind for next time.
- shakow 5y ago> while also being the last thing on earth languages like Node and Java would ever permit developers to do. You sure about that? https://www.npmjs.com/package/mmap-io https://www.npmjs.com/package/mmap-io https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/MappedByteBuffer.html https://docs.oracle.com/en/java/javase/11/docs/api/java.base... mmap is a system call, not some C-exclusive fancy feature. If your language can print something on the screen, there is no reason it can't mmap a file. And if the serialization was the bottleneck, mmap would not have changed anything anyway.
- jcelerier 5y agoI wonder what kind of hardware OP runs on, and which compiler is used. Here on my laptop (Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz), on linux, compiling an eigen example takes 0.7 seconds clang++ -fuse-ld=lld eigen.cpp -I/usr/include/eigen3/ 0,66s user 0,07s system 100% cpu 0,728 total if I put the eigen header in a PCH this drops to 0.1 seconds clang++ -fuse-ld=lld eigen.cpp -I/usr/include/eigen3/ -include-pch 0,08s user 0,02s system 104% cpu 0,099 total I have a hard time seeing how 300 lines of code (vs the 10 lines of the example) would multiply compile times by 300 - here's the example I used #include <iostream> #include <Eigen/Dense> using Eigen::MatrixXd; int main() { MatrixXd m(2,2); m(0,0) = 3; m(1,0) = 2.5; m(0,1) = -1; m(1,1) = m(1,0) + m(0,1); std::cout << m << std::endl; } I develop a GUI software with boost, Qt, and a fair amount of TMP and my average turn-around time from a change in the average file to the program running is between in and 2 seconds. Maybe it could get down to 0.1~ seconds if using C, but there would be so many less checks and guards !
- yoz-y 5y agoHaven’t used Eigen for quite a while, but I do remember it being rather slow to compile once it proliferates. Those templates are clever but also quite taxing.
- pjmlp 5y agoEven so, using another math library would have been the solution then.
- tomn 5y agoThe bits of eigen that make it slow to compile are the same bits that make it both easy to use and fast. I'm sure there are improvements that could be made, but ultimately eigen is big and slow to compile for a reason.
- dthul 5y agoDoes it get significantly slower if you use Eigen's fixed size matrices? I have a C++ project which uses Eigen and some other templates (but nothing crazy) and it is quite slow to compile. Not complaining though because I don't compile often.
- everyone 5y agoI just freakin' hate .h files... Writing pretty much the same thing but twice and in two different places for every function. Why?!? I would love C++ if not for them.
- pjmlp 5y agoThen use modules, VC++ 20219 has the best support, GCC 11 has similar support, it is mostly clang that still had some catch-up to do.
- jart 5y agoOr use traditional C. The x86_64 System V ABI is well defined enough that I'm honestly not sure why we need prototypes. All we need is a compiler that authorizes us to use an ILP64 data model. Then we'll be able to write C the way it was intended to be written without the quadratic header complexity. C++ showed us where that path leads and I truly hope modules happen but it's about as likely as my hopes of restoring the good parts of K&R C.
- pjmlp 5y agoC was already a pre-historic language in 1992, why should I bother with it in 2021, unless I must do so for business reasons? It is the COBOL of systems programming languages and must be dealt exaclty on the same terms.
- efficax 5y agoFor modules in GCC you currently have to build the branch yourself. Not the best experience
- pjmlp 5y agoMissing out on GCC 11 release? https://gcc.gnu.org/gcc-11/changes.html https://gcc.gnu.org/gcc-11/changes.html > Modules, Requires -fmodules-ts and some aspects are incomplete. Refer to C++ 20 Status Naturally some C++20 features aren't still fully there, it isn't as if they aren't coming.
- b0tzzzzzzman 5y agoWhy are all the things I post dead?
- qalmakka 5y agoYesterday I finished a complex library in C++20, which I wrote "Rust style" by following the ISO C++ guidelines. I used 0 `new`, no smart pointers, all by-value returns, move semantics and STL containers. It literally took a month to write, but it worked as expected at the second attempt (first failed due to forgetting a `}` in a format string). I did not see a single segmentation fault and it works fine on both Linux and Windows. Rust and modern C++ give you the expressivity to describe what you want to accomplish to the compiler. You can use the type system to erase certain classes of error entirely, without ever having to worry about something pointing to bad memory or whatever. If you follow certain rules, C++ can be _almost_ as safe as Rust. I love C personally, and I've been coding in it for more than a decade now. It is simple, easy to learn, but it gives you literally zero ways to create abstractions. That could be a good thing, until you realize that almost all complex C codebases reimplement (badly) half of the STL due to libc providing no containers of sort. Linked lists are terrible for performance, yet C programs are pestered with them due to how simple they are to implement. And I won't get started with C strings, which are just a historical BadIdea™. Most C codebases tend naturally to evolve into a mishmash of reinventing the wheel and ugly macro magic. After all it's either that or to risk committing preventable mistakes all over. That's not without saying that C++ is that better - every three years a new release comes out and fixes certain issues so vastly that it completely changes how you are supposed to write code (see string_view, or concepts). That's not without saying that the language is also full of old cruft no one should ever think about using.
- andreidd 5y agoIs the library open source? If so, would you share a link?
- qalmakka 5y agoSorry but the source is closed sadly, so I can't share it. I would like to write a blog post someday about some of the patterns I've used though, I think they could be useful to someone.
- Santosh83 5y agoWould you mind sharing a link to the ISO C++ guidelines you mention?
- refactor_master 5y agoMeanwhile, a data science project in python runs right up until it doesn’t, and the only help you have is heuristics for why your data maybe was bad. I almost sort of envy compilation at times.
- gargalatas 5y agowhy someone would compare python wih c++?
- jart 5y agoIf you want a good case study in repositories that build quickly then check out cosmopolitan libc. Typing `make -j16` it compiles 15,383 .o objects, 69 .a static libraries, and 548 executables, 297 of which are test executables, which are also run by the make command, and all that takes just 40 sec.
- qayxc 5y agoMinor nit-pick: wouldn't that rather be make -j`nproc`? Not everyone has an 8-core CPU with SMT or a 16+-core CPU respectively.
- jart 5y agoI measured that on an Intel(R) Core(TM) i9-9900. I understand that not everyone has the privilege of being able to afford a $1,000 computer. However those people usually aren't coders. It'd be great if I had one of those 64 core "specialist" workstations that people like googlers use to be able to compile projects like TensorFlow in a reasonable amount of time. My project would probably build in seconds. But alas I don't, so I need to forgo eigen and template metaprogramming.
- kayamon 5y agoNot everyone runs UNIX and sh either.
- socialdemocrat 5y agoI can relate a lot to this as an old school C++ developer (not anymore). I also remember switching a project over to pure C some years ago and found it a lot more pleasant to work with. But I combined it with Lua so I could use Lua for high level constructs C is not so good at. Anyway I suspect that in a few years Zig will be the choice in scenarios like this. It gives you fast compilation times and low level access like C while being a lot safer, but without adding C++ level complexity. That is an important sweet spot to hit.
- nmeofthestate 5y ago>linking a big C++ project with lots of dependencies and some template magic can easily take several minutes. dreams wistfully of C++ build that only takes several minutes
- mauricedenassau 5y agoWhile I can understand the feeling of "bloat" in C++, I can't help but be put off each time I have to implement something even remotely serious in C. I have to reinvent the wheel everytime while in C++ I can have access to high quality implementation in the STL.
- thrower123 5y agoIt seems like most of the issues with C++ are solved by not using the parts that suck. You can still write C++ as C-with-classes/RAII/operator overloads, and leave all the smart pointers and template meta-programming and boost (do people still use boost?) magic at the door.
- phendrenad2 5y agoOften the C vs C++ debate comes down to a religious war where the C side is arguing that their hammer is best for pounding in nails and the C++ side is arguing that their screwdriver is best for screwing in screws. I don't believe they're suited for the same purposes at all. If you're writing code where you need to interface with the hardware directly (embedded), or manipulate things in kernel-space efficiently (OpenGL, audio, games), then C makes a lot of sense (and if you try to write C++, you'll end up using C anyway, and your attempts to shoehorn in C++ features will be ineffective). If you're writing a high-performance C++ version of a Python web service, C++'s nicer memory management and string handling will be very VERY useful, not to mention object types for serializing/deserializing data.
- kaba0 5y agoI don’t think this argument holds up. You can create very good abstractions over the lower level libs, like OpenGL. There is no need to call it “naked” like in C.
- germandiago 5y agoI can understand the point about compile times. For the rest, C++ is just superior and you cand do what you would do in C, just safer and add constexpr, consteval and careful templates for more type safety plus more type safety by default.