4 ms·
There is surely something wonderful about the kind of C++ programmer who figures that, since their unusable broken garbage compiled it's probably correct. Reme
by tialaramex 3y ago
There is surely something wonderful about the kind of C++ programmer who figures that, since their unusable broken garbage compiled it's probably correct.
Remember unlike most languages you'd be familiar with C++ has IFNDR, which has been jokingly referred to as "False positives for the question: Is this a C++ program?". A conforming C++ compiler is forbidden from telling you in some† unknown number of cases that it suspects what you've written is nonsense, it just has to press on and output... something. Is it a working executable? Could be. Or maybe it's exactly like a working executable except it explodes catastrophically on Fridays. No way to know.
† The ISO standard does identify these cases, but they're so vague that it's hard to pin down everything which is covered. My guess is that all or most non-trivial C++ software is actually IFNDR these days. Just say No to the entire language.
- lmm 3y ago> A conforming C++ compiler is forbidden from telling you in some† unknown number of cases that it suspects what you've written is nonsense, it just has to press on and output... something. It's not quite that bad - a conforming C++ compiler is permitted to error out and not compile the program. It just doesn't have to.
- tialaramex 3y agoMany of the cases of IFNDR are semantic constraints, especially in C++ 20 and beyond. As a result of being semantic constraints it's generally impossible to diagnose this with no false positives. The ISO standard forbids such false positives so...
- lmm 3y agoCan you give a more concrete example of the kind of thing you're talking about? Like, if you try to sort something and your comparator implementation for that type is not transitive, the compiler can silently produce a broken binary? Surely in the undecidable cases the compiler is allowed to produce a binary that errors cleanly at runtime if you did in fact violate the semantic constraint, and any sane implementor would do that. (Not that any sane person would ever write a C++ compiler...)
- tialaramex 3y ago> Like, if you try to sort something and your comparator implementation for that type is not transitive, the compiler can silently produce a broken binary? It's not merely about whether your comparisons are transitive, the type must exhibit a total ordering or your sort may do anything, including buffer overflow. > Surely in the undecidable cases the compiler is allowed to produce a binary that errors cleanly at runtime if you did in fact violate the semantic constraint, I don't think I know how to prove it, but I'm pretty sure it's going to be Undecidable at runtime too in many of these cases. Rice reduced these problems to Halting, which I'd guess means you end up potentially at runtime trying to decide if some arbitrary piece of code will halt eventually, and yeah, that's not helpful. I've written about it before, but I should spell it out: The only working alternative is to reject programs when we aren't sure they meet our constraints. This means sometimes we reject a program that actually does meet the constraints but the compiler couldn't see it. I believe this route is superior because the incentive becomes to make that "Should work but doesn't" set smaller so as to avoid annoying programmers, whereas the C++ incentive is to make the "Compiles but doesn't work" set larger since, hey, it compiles, and I see Rust's Non-Lexical Lifetimes and Polonius as evidence for this on one side, with C++ 20 Concepts and the growing number of IFNDR mentions in the ISO standard on the other side.
- lmm 3y ago> the type must exhibit a total ordering or your sort may do anything, including buffer overflow. Sure. But there's no requirement for the compiler to be a dick about it, and hopefully most won't. > I don't think I know how to prove it, but I'm pretty sure it's going to be Undecidable at runtime too in many of these cases. Rice reduced these problems to Halting, which I'd guess means you end up potentially at runtime trying to decide if some arbitrary piece of code will halt eventually, and yeah, that's not helpful. At runtime can't you just run the code and let it halt or not? Having your program go into an infinite loop because the thing you implemented didn't meet the requirements is not unreasonable. I'm sympathetic to the idea that there could be a problem in this space, but without a real example of a case where it's hard for a compiler to do something reasonable I'm not convinced. > I've written about it before, but I should spell it out: The only working alternative is to reject programs when we aren't sure they meet our constraints. This means sometimes we reject a program that actually does meet the constraints but the compiler couldn't see it. > I believe this route is superior because the incentive becomes to make that "Should work but doesn't" set smaller so as to avoid annoying programmers, whereas the C++ incentive is to make the "Compiles but doesn't work" set larger since, hey, it compiles, and I see Rust's Non-Lexical Lifetimes and Polonius as evidence for this on one side, with C++ 20 Concepts and the growing number of IFNDR mentions in the ISO standard on the other side. Meh. I'm no fan of the C++ approach, but I'd still rather see C++ follow through on its strategy than half-assing it and becoming a watered-down copy of Rust. People who want Rust know where to find it.
- _gabe_ 3y agoCan you give an example of non-C++ code that a modern compiler (MSVC, clang, g++ or something) successfully compiles with no diagnostics? I’m genuinely curious. If not, this just sounds like more C++ FUD because the spec doesn’t define everything under the sun and allows a certain amount of leeway to compilers for things like emitting different error diagnostics.
- LegionMammal978 3y agoConsider: #define _FOO int main() {} Per the C++ standard ([lex.name]/3), this program is ill-formed: > In addition, some identifiers appearing as a token or preprocessing-token are reserved for use by C++ implementations and shall not be used otherwise; no diagnostic is required. [...] Each identifier that contains a double underscore __ or begins with an underscore followed by an uppercase letter is reserved to the implementation for any use. Thus, the compiler theoretically has the liberty to emit whatever it wants for this program. Neither GCC nor Clang produces a warning under -std=c++20 -Wall -Wextra (Clang only produces a -Wreserved-macro-identifier under -Weverything), and MSVC doesn't produce a warning under /std:c++20 /Wall. In practice, most examples of ill-formed programs where compilers issue no warnings occur with discrepancies between different source files that are linked together; e.g., declaring a function as inline in one file but non-inline in another, or declaring a function with two different sets of default arguments in different files, or defining the same non-inline variable or function in different files, or defining the same inline function differently in different files.
- tialaramex 3y agoDon't forget since C++ 20 all programs which rely on a concept but don't fulfill the (unchecked) semantic constraints of that concept are ill-formed. This allows C++ to truthfully say that C++ programs have the same properties as Rust programs in this regard, because although most real world C++ may fail those constraints and they aren't checked, as a result those aren't technically C++ programs at all so the fact they're broken and don't do what their programmers expected is magically not the fault of C++ even though the compiler seemed fine with it.
- 3y ago