4 ms·
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. So
by syockit 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. 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.
- adrianN 5y agoNaturally you first implement your algorithm together with a correctness proof inside the turing-complete type system. After that writing the real implementation can be left as an exercise for the compiler :)
- pjmlp 5y agoBy using contracts, unfortunately they were postponed to C++23.
- patrick451 5y agoClearly, if you missed a minus sign somewhere, your type system is not powerful enough (>:<)