Hacker News new | ask | show | jobs
by kris-jusiak 1168 days ago
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.
3 comments

I _think_ you cannot conclude from a constexpr not having UB at compile time that it won’t have UB at runtime.
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.
(nevermind)
Successful compilation of UB is explicitly disallowed by the standard in a constexpr context
Thanks, I wasn’t aware. Although that seems to be restricted to core language UB and not include standard-library UB: https://stackoverflow.com/a/72494688/623763
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.

Of course, cannot be relied upon.

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).

Doesn't it depend on the actual values?

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.

That is a complete misunderstanding.

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).
"A constexpr function can very well take an input, and it can invoke UB based on the runtime value of that input. "

No, it cannot. It is not constant if it does that. As such, it cannot be used in in any context that requires a constant expression. It explicitly says the operations in a constexpr may not produce undefined behavior.

That isn't "may not produce undefined behavior except if you pass the wrong values at runtime or don't evaluate it". It says: "An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (6.8.1), would evaluate one of the following expressions:

an operation that would have undefined behavior "

In your case, the evaluation would evaluate (at runtime) an operation that would have undefined behavior.

It is true that where you do not require a constant expression, it does not require it be constexpr at all, but the question is whether an expression that produces undefined behavior is constexpr is "no".

The standard even clarified this to say that foo is simply considered non-constant in your example (IE it's not constexpr ).

IE see defect report 695

"The consensus of the CWG was that an expression like 1/0 should simply be considered non-constant; any diagnostic would result from the use of the expression in a context requiring a constant expression. "

In your example, foo is non-constant as used. It is not constexpr.

You're both right, but you're talking about different things.

If you use the same values at run time as you use in the tests, the tests predict the behavior as desired.

If you use different values at run time as you use in the tests, the tests do not predict the behavior as desired.

Unless you used std::is_constant_evaluated() and the code running at runtime was not the one tested at compile time
At the very minimum, you can simply do is_constant_evaluated to change behavior depending on runtime vs compile-time...
>constexpr has to checked for leaks and UB so as long as there is coverage at compile-time

Source ?I am not sure constexpr give any garanty regarding UB and/or leaks

Constexpr at compile times gives that guarantee, not constexpr in general. Hence static assert to force comptime evaluation
> Constexpr at compile times gives that guarantee

Can you point to a source for that ? I am not trying to be pendantic, but genuilly curious