I find it annoying when people say you “need” some best practice. The things you actually need are generally enforced by the tools (compiler errors in this case?). Everything else is subjective and/or depends on your use case.
I find it annoying when people say you “need” some best practice. The things you actually need are generally enforced by the tools (compiler errors in this case?). Everything else is subjective and/or depends on your use case.
No. This is specifically C++ which has IFNDR ("Ill-formed, No Diagnostic Required") which means the ISO document says there are things (a lot of things it turns out, WG21 appears to have given up even trying to enumerate them) which aren't valid C++ - and so you mustn't do them because the resulting program is meaningless - and yet the compiler isn't expected to detect them and reject your program.
Henry Gordon Rice wrote an important PhD thesis about computation in like 1951. Rice's Theorem says that all non-trivial semantic properties are Undecidable. But we want semantic properties! So, if your programming language is going to have non-trivial semantic properties then you have two practical options:
1. Accept all programs which may have the desired semantic properties, since you can't always decide which those are, you give up and accept programs where you aren't sure, these programs are nonsense, but too bad. That's C++ with IFNDR
2. Reject all programs which may not have the desired semantic properties, since you can't always decide which those are either, you give up and spit out a compiler error when you aren't sure. If a program is rejected maybe the human programmer will rewrite it so that it's acceptable, which is a burden on them. That's what Rust does.
For example maybe when mainprogram.cpp is compiled, the variable max_princesses is defined as the literal 10, but when the princess.cpp file is compiled, perhaps several minutes later, the variable max_princesses is now defined as the literal 0.5 - that's not even the same type! What happens? The ODR means that's IFNDR, so instead of C++ needing to somehow guarantee that this definitely is caught by compilers [these days some compilers will catch some ODR violations but it's not all of them and not always] the standard just says too bad, that's not a valid C++ program so whatever it does is your problem.
A really shiny modern example of IFNDR is C++ 20 Concepts semantic requirements. See, functionally C++ 20 Concepts are just syntax matching, but their names imply semantic value, the standard says they do have semantic value, but it's not actually enforced by the tooling, so syntactically float (a floating point number) matches the concept std::totally_ordered - but of course floats aren't actually totally ordered, that's silly. How do they square this circle? IFNDR. Using a type which matches the syntax, but doesn't fulfil the semantic criteria means your C++ program is ill-formed, but the compiler wasn't expected to tell you about that, your program is just gibberish, it might do anything - after all, floats aren't in fact a totally ordered type.
So of your two examples, here's what I would expect. In the first one, I would expect that either everything would work, or I'd get a syntax error, depending on which definition was actually used at the time I compiled the line in question. Are there examples where anything else happens besides those two options? (OK, I guess there's also the option that two different compilation units have two different definitions at the time I compile them, and then I link them together. Best case the linker catches it; worst case I'm doing floating point operations on what is sometimes an integer variable, and I can see some very weird things happening from there.)
And in the second example, I'd expect everything to work right up until I tried to sort (or whatever) the floating point numbers, at which point it would either work, or fail to sort, or infinite loop, depending on the exact floating point values that the program was operating on. Here I could see there being other options, depending on exactly what the program was trying to do, but not dramatically different. And, would it do anything but work as expected if there were no NANs or negative zeroes or something exotic like that?
That is: Despite the "it can do anything" statements, in practice, with production compilers, does it do completely unreasonable things? Or does the "anything" it does have some reasonableness to it?
Yeah, I know, I'm not guaranteed that. In practice, I don't actually care how my code might break on Windows 3000 with its new 197-bit bytes. I care some about problems on platforms and compilers that it's reasonably likely to need to run on someday. (I'd care more if I were writing library code, and even more if I were writing code for the STL. But I'm not, and while portability is desirable, it's not the only input to decisions.)
The compilers aren't intentionally breaking your code, but they have no responsibility for what happens once you break the rules. I care about Correctness too much for this to be acceptable. If I wrote 100 programs and ten are faulty, I'd rather have twenty errors, half of which are false positives and half catch my ten mistakes, than five errors and five of the "working" programs compile but have mysterious bugs because they're faulty.
I haven't looked at this specific list yet, but I am yet to see a C++ project in which code works just by following what the tools tell you.