In C++23, the [[assume(conditonal-expression)]]
attribute makes it so that if conditional-expression doesn't evaluate to true
, the behavior is undefined.
For example:
int div(int x, int y) {
[[assume(y == 1)]];
return x / y;
}
This compiles to the same code as if y
was always 1
.
div(int, int):
mov eax, edi
ret
As commenters have pointed out, this is not a required optimization; it's just what GCC happens to do with the information that anything but y == 1
would be UB.
It would be valid for compilers to completely ignore all assumptions.
Compilers are required to diagnose all undefined behavior in constant expressions1), but is this reasonable? For example:
constexpr bool extremely_complicated(int x) {
bool result;
// 10,000 lines of math ...
return result;
}
constexpr int div(int x, int y) {
// This should result in a compiler error when the compiler is unable to prove
// what extremely_complicated(x) returns.
// extremely_complicated(x) is not evaluated, so it needs to be able to
// prove the result without evaluating the function.
[[assume(extremely_complicated(x))]];
return x / y;
}
constexpr int quotient = div(4, 2);
Is it still a problem even if the compiler has no way of proving whether an assumption would evaluate to true
? It obviously can't solve the halting problem.
How exactly do assumptions and constant expressions interact? [dcl.attr.assume] doesn't have any wording on this.
1) Note: extremely_complicated(x)
is not a constant expression, but it's located in an assumption whose failure would result in UB within a constant evaluation of div(4, 2)
, which is a constant expression. It is generally said that UB in a constant expression needs to be diagnosed.
There is a specific exception for assume
in the final C++23 draft.
[expr.const]/5.8
(with references deciphered)
An expression
E
is a core constant expression unless the evaluation ofE
, following the rules of the abstract machine ([intro.execution]
), would evaluate one of the following:
- ...
- an operation that would have undefined behavior as specified in
[intro]
through[cpp]
, excluding[dcl.attr.assume]
;
So a compiler doesn't need to judge the veracity of assumptions in order to judge the constantness of expressions. If you write
constexpr int g() {
[[assume(false)]];
return 5;
}
then g()
may or may not be a core constant expression (it is unspecified whether [[assume(E)]];
disqualifies an expression for being constant if E
both is allowed in the constexpr
context and doesn't return true
). If you further write
int main() {
constexpr int x = g();
}
there are two cases. If the implementation has decided g()
is not a core constant expression (as it is free to do so), it is required to give a diagnostic. If the implementation has decided g()
is a core constant expression, the program has undefined behavior.
So you see that compilers have been given an out. A false assumption in a constexpr
context can be undefined behavior and not a diagnostic at the implementation's choosing. An implementation could just choose to never check the assumptions in constant expressions. If an assumption then turns out to be false, the program appearing to compile and run successfully (if incorrectly) is just a manifestation of the resulting undefined behavior.