-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
317 additions
and
297 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
--- | ||
title: Assertions | ||
--- | ||
|
||
{{< include /content/_common.qmd >}} | ||
|
||
## Introduction | ||
|
||
The `bit_assertion.h` header has two replacements for the standard [`assert`] macro --- they allow for an additional string output that you can use to print the values of the variables that triggered any failure. | ||
|
||
```cpp | ||
bit_assertion(condition, message) // <1> | ||
bit_debug_assertion(condition, message) // <2> | ||
|
||
``` | ||
1. This is very similar to the standard [`assert`] macro --- the assertion is always checked **unless** the `NDEBUG` flag is set at compile time. | ||
2. On the other hand, this type of assertion is **only** checked if the `DEBUG` flag is set at compile time. | ||
Assuming a check is "on", in all cases, if `condition` evaluates to `false`, these both call the macro: | ||
```cpp | ||
bit_exit(message) | ||
``` | ||
The `bit_exit(message)` macro passes the `message` and the source code location of the failure to `bit::exit(...)`. | ||
That function prints the source code location of the failure along with the `message` payload and then **exits** the program. | ||
|
||
The `bit::exit` function needs source code location parameters (the filename, the line number, and the function name), and the `bit_exit` macro automatically adds those. | ||
You typically use the `message` argument to print the values of the variables that triggered the failure. | ||
The message can be anything that can be formatted using the facilities in [`std::format`]. | ||
|
||
::: {.callout-tip} | ||
# Microsoft compiler | ||
Microsoft's old traditional preprocessor is not happy with these macros, but their newer cross-platform compatible one is fine. | ||
Add the `/Zc:preprocessor` flag to use that upgrade at compile time. | ||
Our `CMake` module `compiler_init` does that automatically for you. | ||
::: | ||
|
||
## Compiler Flags | ||
|
||
Flag | Description | ||
------------- | ----------- | ||
`BIT_DEBUG` | If you set the `BIT_DEBUG` flag, the library will perform demanding but potentially useful safety assertions on indices, size equality checks, etc. Otherwise, all the `bit_debug_assertion` calls are no-ops. | ||
`NDEBUG` | This works exactly like it does for the standard [`assert`] macro. If it is set then all `bit_assertion` calls are np-ops. | ||
: {.bordered .striped .hover .responsive tbl-colwidths="[20,80]"} | ||
|
||
::: {.callout-note} | ||
# Compiler Flag Consistency | ||
If you set the `NDEBUG` flag, we make sure that the `BIT_DEBUG` flag is not set. | ||
::: | ||
|
||
## Examples | ||
|
||
[Example --- Snippet from the `bit::vector<>::set` method]{.bt} | ||
```cpp | ||
/// @brief Set the element at index `i` to 1. | ||
constexpr bit::vector &set(std::size_t i) | ||
{ | ||
bit_debug_assertion(i < m_size, "index `i` = " << i << " must be < `m_size` which is " << m_size); | ||
... | ||
} | ||
``` | ||
Here `m_size` is holds the size of the vector --- so we must have `i < m_size` | ||
To check every element access, set the `BIT_DEBUG` flag during compiles. | ||
If the assertion fails, the program exits with an error message that gives the offending values. | ||
The `bit_debug_assertion` line expands to *nothing* if the `BIT_DEBUG` flag is not set during compiles. | ||
[Example --- Message from an assertion failure]{.bt} | ||
```cpp | ||
#undef NDEBUG // <1> | ||
#define BIT_DEBUG | ||
#include <bit/bit.h> | ||
int main() | ||
{ | ||
std::size_t n = 12; // <2> | ||
bit::vector<> v(n); | ||
v.set(n); // <3> | ||
std::cout << v << "\n"; | ||
} | ||
``` | ||
1. For the sake of the example we added code lines to make sure the `bit_assertion` is triggered. In normal usage, these flags are passed through the compiler command line. | ||
2. Construct a vector of size 12 and then attempt to set the "last" element. | ||
3. A deliberate but typical *off-by-one* index error as the valid indices are from 0 to `n-1`, which is 11. | ||
|
||
[Output]{.bt} | ||
```sh | ||
BIT ASSERTION FAILURE: | ||
Function 'set' (vector.h, line 910): | ||
Statement 'i < m_size' is NOT true: Index i = 12 must be < `m_size` = 12 | ||
``` | ||
The program will then exit. | ||
|
||
## Design Rationale | ||
|
||
### `bit_debug_assertion` | ||
|
||
In the development cycle, it can be helpful to range-check indices and so on. | ||
However, those checks are expensive and can slow down numerical code by orders of magnitude. | ||
Therefore, we don't want there to be any chance that those verifications are accidentally left "on" in the production code. | ||
The `bit_debug_assertion(...)` form covers this type of verification. | ||
Turning on these checks requires the programmer to take a specific action --- namely, she must set the `BIT_DEBUG` flag during compile time. | ||
|
||
For example, here is a pre-condition from a hypothetical `dot(Vector u, Vector v)` function: | ||
```cpp | ||
bit_debug_assertion(u.size() == v.size(), "Vector sizes {} and {} DO NOT match!", u.size(), v.size()); | ||
``` | ||
This code checks that the two vector arguments have equal length --- a necessary constraint for the dot product operation to make sense. | ||
If the requirement is not satisfied, the code will exit with an informative message that includes the size of the two vectors. | ||
The check here is **off** by default, and you need to do something special (i.e., define the `BIT_DEBUG` flag at compile time) to enable it. | ||
The idea is that production code may do many of these dot products, and we do not generally want to pay for the check. | ||
However, enabling these sorts of checks may be very useful during development. | ||
The `bit_debug_assertion(...)` macro expands to nothing **unless** you set the `BIT_DEBUG` flag at compile time. | ||
### `bit_assertion` | ||
On the other hand, there are some checks where the cost of the assertion is slight when compared to the work of the function. | ||
Leaving these checks on in production is not likely to impose much of a performance penalty. | ||
For example, a pre-condition for a matrix inversion method is that the input matrix must be square. | ||
Here is how we do that check in an `invert(const Matrix& M)` function: | ||
```cpp | ||
bit_assertion(M.is_square(), "Cannot invert a {} x {} NON-square matrix!", M.rows(), M.cols()); | ||
``` | ||
We can only invert square matrices. | ||
The `M.is_square()` call checks that condition and, on failure, exits the program with a helpful message. | ||
The check cost is very slight compared to the work done by the `invert(...)` method, so leaving it on even in production code is not a problem. | ||
|
||
The check here is **on** by default, and you need to do something special (i.e., define the `NDEBUG` flag at compile time) to disable it. | ||
|
||
The `bit_assertion(...)` macro expands to nothing **only if** you set the `NDEBUG` flag at compile time. | ||
|
||
The decision to use one form vs. the other depends on the cost of doing the check versus the work done by the method in question. | ||
A primary use case for `bit_debug_assertion` is to do things like bounds checking on indices --- from experience, this is vital during development. | ||
However, bounds-checking every index operation incurs a considerable performance penalty and can slow down numerical code by orders of magnitude. | ||
So it makes sense to have the checks in place for development but to ensure they are never there in release builds. | ||
|
||
::: {.callout-note} | ||
# Macro-land | ||
We are in macro land here, so there are no namespaces. | ||
Typically, macros have names in caps, but the standard `assert` does not follow that custom, so neither do these `bit_assertion` macros. | ||
::: | ||
|
||
### See Also | ||
[`assert`] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,4 +82,4 @@ Col 2: [1 0 1 0] | |
|
||
### See Also | ||
[`vector::reference`] \ | ||
[`bit_assert`] | ||
[`bit_assertion`] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.