What are the parts of C++ that make C unsuitable to be used for scientific libraries?
Someone could argue the merit of C (or Fortran for that matter) to be simplicity. However, why do developers choose to expand onto C++, rather than remain in C?
What are the parts of C++ that make C unsuitable to be used for scientific libraries?
Someone could argue the merit of C (or Fortran for that matter) to be simplicity. However, why do developers choose to expand onto C++, rather than remain in C?
Nothing makes C unsuitable for scientific libraries. There are tons of very useful libraries written in C, and C's ABI is compatible with almost every other language unlike C++.
The primary reason for using C++ is simply convenience. You don't have to be so explicit and careful about everything you're doing (especially concerning memory management), and there are many features which let you reduce how much duplicate/redundant code you have to write.
std::map
, rather than having to write 8290 lines of code (the GCC implementation of std::map
).
$\endgroup$
Commented
Jul 15, 2022 at 19:00
std::{x}
doesn't fit what I need and I have to resort to at least interfacing with a C-like library interface (i.e. HDF5, PETSc, MPI, METIS/PARMETIS, etc.). I could just as easily say "I'm not going to write a massively parallel sparse matrix library so I'm just going to use PETSc".
$\endgroup$
Commented
Jul 16, 2022 at 1:28
C++ is a vastly larger language. The C++20 language standard has 1610 pages, the C11 language standard has 470 pages (without appendices in both cases). In that difference of more than a thousand pages are:
A vastly larger standard library. Of course you can express everything in the C++ standard library (containers, algorithms, I/O, ...) in C as well, but why would you spend years of your life writing code that comes as part of your compiler?
A vastly easier system to manage large code bases. C++ has many mechanisms that allow for convenient encapsulation (classes, polymorphism, namespaces, now modules) that require inconvenient and laborious workarounds in C. As above, it's of course possible to all of this in C (as it is in assembler, for that matter), but it takes work and time and effort and debugging skills -- why would you put in the time and money to do that if you didn't have to?
In the end, if your goal is to write a 200-line program, I'd say choose whatever you want. Doing easy things is easy in any language. But most of the world operates on programs that have hundreds of thousands or millions of lines of code; these are hard, and you want as much support as you can to minimize the potential for bugs and maximize the time you can actually spend on the stuff that matters rather than re-writing core data structures that are already available and tested.
The actual question is that developers ask is usually not "which available language is better for a computational task at any particular point of time" but "what will get the job done with the least effort".
Fortran77 is still used in numerous computational engineering codes. That is because some extremely complex models have been developed in that language and tested extensively over many years. Rewriting those in a more modern language (even Fortran90) will not only be a waste of time, but will potentially introduce bugs. So people continue to use those.
C++ and C were popular when new codes started moving away from Fortran. As a result, there is now a huge legacy code base in these languages. The effort that will be needed to move to more modern languages, e.g., Rust, is not worth it at this time. Once Rust has proven itself and has enough libraries, we will see new generation codes being developed in that (or whatever language becomes the most popular next).
Some of the biggest reasons I choose C++ over C for non-trivial scientific computing codebases time and time again are:
Item one and two have some pretty clear benefits when used right, so I will focus on an example for the last point. The example I will use stems from a somewhat recent research project I was involved in where we were developing a distributed spacetime discontinuous Galerkin codebase.
In this project, we were trying to architect the codebase to be able to run in a distributed manner using MPI. However, we tried to make some optimizations where we would spawn separate non-MPI processes and use shared memory segments via Boost.interprocess to exchange data between MPI and non-MPI processes on the same node.
Now, Boost.interprocess had functionality that allowed one to allocate data structures on shared memory segments but you had to carefully design the data structures to do it. What we already had were existing data structures we used for localized, serial work and the ideal goal from both a usability and performance view was for us to try and make them also usable with shared memory instead of constantly serializing and deserialize things.
After studying the Boost.interprocess library, I wrote a small amount of new code using ideas from type traits and SFINAE to extend these data structures so they could either live in a serial environment or live in a shared memory segment, depending on how a template parameter was set. The implementation did not break older code using the data structures and made it much easier to write new, correct code to communicate our data structures between an MPI and non-MPI process.
This could not have been done with C.