The user defined attribute [[assumes(expression)]]
Specifies that the given expression is assumed to always evaluate to true at a given point in order to allow compiler optimizations based on the information given.
where if the expression is false, it invokes runtime undefined behavior.
However, the same page says:
One correct way to use them is to follow assertions with assumptions:
assert(x > 0); // trigger an assertion when NDEBUG is not defined and x > 0 is false [[assume(x > 0)]]; // provide optimization opportunities when NDEBUG is defined
I'm confused by this statement. If NDEBUG
is not defined, and x > 0
is false, the condition in the assume would be false, which triggers undefined behavior.
As far as I understand this, it means the compiler can assume that the expression is true, and remove the assert entirely. That would mean the "correct" usage isn't actually correct, so I suspect I'm misunderstanding something here. Is the UB invoked by [[assume]]
not permitted to "time-travel" like UB usually is?
How does [[assume]]
work exactly?
How does
[[assume]]
work exactly?
It is roughly equivalent to
if (!(x > 0)) {
std::unreachable(); // trigger UB
}
Is the UB invoked by [[assume]] not permitted to "time-travel" like UB usually is?
It is allowed, but assert
prevents the UB from being reached:
assert(x > 0); // trigger an assertion when NDEBUG is not defined and x > 0 is false
[[assume(x > 0)]]; // provide optimization opportunities when NDEBUG is defined
is roughly equivalent to:
#ifndef NDEBUG
if (!(x > 0)) {
warn_user_assert_break("x > 0");
std::abort(); // No return function
}
#endif
[[assume(x > 0)]];
or
#ifndef NDEBUG
if (!(x > 0)) {
warn_user_assert_break("x > 0");
std::abort(); // No return function
}
#endif
if (!(x > 0)) {
std::unreachable(); // trigger UB
}
So, when NDEBUG
is not defined, and the precondition fails, then std::abort
is executed, and you don't reach the assume
(which would break the precondition too and trigger UB).
Compiler might even make the assumption by itself with the above if
.
When NDEBUG
is defined, there is only [[assume(x > 0)]]
, and if the precondition fails, you have UB, and time-travel can indeed happen.