> A core constant expression is any expression whose evaluation would not evaluate any one of the following:
> 8. an expression whose evaluation leads to any form of core language undefined behavior (including signed integer overflow, division by zero, pointer arithmetic outside array bounds, etc).
This does not really "guarantee" anything. You can still have as much UB (incl leaks) as you want, as long as they are not evaluated at compile-time. i.e. it's at best equivalent to running valgrind.
The point is that the code is only checked for UB with the arguments it is given at compile time. There is no guarantee that it can't invoke UB with other arguments it might receive at runtime.
For example, here is a modification of the original program that does invoke UB, but compiles just fine:
That only works if you know the values at compile-time though:
constexpr int foo(int x) {
return 1024*1024*1024*x;
}
int main()
{
int y;
std::cin >> y;
static_assert(foo(1)); //all good
foo(y); //oops, UB if user enters 7
}
Yeah, the tests will only fail if the tests trigger UB. It's like all testing, it only detects issues if you trigger the issues in the tests. Using static_assert as your test system obviously doesn't obviate the need for writing good tests.
Many people in this thread think that if a constexpr function can be called at compile time successfully, it will also be guaranteed not to have UB at runtime, in general.
I was pointing out that this is only true for the cases you actually test, not the general case.
Even then, it's not fully true, as a function may have different behavior at runtime as opposed to compile time (e.g. because of multi-threading), and so it may display UB even when called with the same arguments that didn't display UB at compile-time.
Overall, this static_assert trick is just a nice way to make sure your tests don't accidentally pass while still invoking UB, to protect from false negatives.
Aha, I think I see. Upon reflection, I do remember that some people seem to think that UB is a property of the code, not the execution; that a piece of code either "contains UB" or does not. I suppose it makes sense then that some people may think that code which works under constexpr can't "contain UB" and get the wrong idea.
You can't call an expression whose value is IO-dependent in a compile-time context, obviously.
The tweet seems to imply that if you can call your functions at compile time, they will not present UB at runtime either. I am trying to point out that that is not the case at all.
I hope there’s also some text in the standard prohibiting implementations from allowing any other expressions as a constant expression (which they otherwise could as a language extension), and thus requires compilation failure for such expressions?
Pragmatically, you can't stop extensions; if the fine print for `--std=cool++23` says that this mode is not actually C++23-compliant, nearly nobody will ever notice or care. Pragmatically, if a popular compiler makes `--std=cool++23` the default, and requires `--std=C++23 --iso-eic-jtc1-sc22-wg21 --pompous` to get standard-conforming behaviour, nearly nobody will do that; instead they will complain that other compilers lack the extensions.
Extensions can be standard-compliant, in the sense that they don’t violate any prescription by the standard, and thus a program cannot assume their absence. My question was whether the standard actually takes care to render the acceptance of constexprs-with-UB non-standard-compliant. That is, in addition to “must accept X”, does it also say “must only accept X”?
constexpr has to checked for leaks and UB so as long as there is coverage at compile-time (static_assert + constexpr) I would assume there shouldn't be neither leaks nor UB. But the context is limitted where that can be applied and actually compiles. For example, there is no way to do it with global variables but with limited scope that's possible.
I guess so, can't think of an example now but I'm pretty sure there are subtle corner cases (as always) and it depends on the testing, coverage and potential limitations of checking things at compile-time, though, IMHO, the technique is promissing and can help with a lot of use cases but defo not everything.
I think it's worth pointing out that your statement is true by definition, perhaps not true by implementation in these cases. It's not like UB produces _random_ behavior, it's just not specified what compilers _should_ do in those cases.
The standard explicitly disallows compiling of UB for constexpr.
Otherwise, what you write is provably correct - UB is not statically decidable in all cases (and depending on the type of UB, not even in a lot of cases).
If your compile time evaluations don't trigger signed integer overflow (or any other UB) does it follow that at runtime you couldn't pass a parameter that would trigger signed overflow?
I mean it's still useful because at least you know your test code is not artificially passing because of some UB makes it look like passing
> it's still useful because at least you know your test code is not artificially passing because of some UB makes it look like passing
Right, that's the extent of what this does. When I saw it on r/cpp I thought OK, somebody realised now they can make their C++ tests work as a reasonable person would expect, or perhaps realised that without this C++ tests are almost worthless because they can invoke Undefined Behaviour silently.
But increasingly I suspect the OP mistook this for a breakthrough in correctness which it isn't, otherwise why post it to HN?
On the other hand, the prohibition on UB for constexpr doesn't reach up to where IFNDR lives, so I'd guess most non-trivial C++ software is technically nonsense with no defined meaning as a result of IFNDR regardless of how many or few unit tests were written or whether they use constexpr to prohibit Undefined Behaviour. A cheerful thought.
[Ill-Formed, No Diagnostic Required: A recurring statement in the C++ ISO document which basically says if you did this then too bad, that's not a well-formed C++ program, however your compiler may not notice that this isn't a C++ program, so, your program might compile, and even execute, but what if anything happens when you run it isn't specified in this ISO standard, good luck.]
No, it doesn't depend on the values.
Yes, it does follow.
The compiler is not allowed to compile constexpr code that could produce UB at runtime. period. :)
That is, conditional constepxr code that depends on values and could produce UB is not valid constexpr code, and a compiler is not supposed to compile it.
This is very explicit in the standard.
Think of it as a statically decidable set of code.
Now, it wouldn't shock me if compilers don't achieve this right now, but the standard is clear that constexpr code may not contain operations that could produce undefined behavior at runtime.
A constexpr function can very well take an input, and it can invoke UB based on the runtime value of that input.
What the standard prohibits is compile-time evaluation of an expression which invokes UB. So, if you actually call your function at compile-time with a constexpr value that ends up invoking UB in the function (say, an integer overflow), THEN the standard mandates that the compiler throw an error rather than compiling some random value in.
For example:
constexpr void foo(int x) {
std::cout << 1024 * 1024 * 1024 * x;
}
int main() {
static_assert(foo(100)); // will fail because computing 1024 * 1024 * 100 is signed integer overflow, which is UB
foo(100); // invokes UB at runtime; in practice, will perhaps print some overflowed value
}
Edit: I should also add that you can very well invoke UB in a constexpr expression if it is standard library UB and not core language UB (e.g. if you try to pop() from an empty std::vector).
Point 8:
> A core constant expression is any expression whose evaluation would not evaluate any one of the following:
> 8. an expression whose evaluation leads to any form of core language undefined behavior (including signed integer overflow, division by zero, pointer arithmetic outside array bounds, etc).