# PR: Add `POCKETFFT_NO_EXCEPTIONS` option for builds without exception handling Repo: https://github.com/mreineck/pocketfft Branch base: `cpp` (upstream master) --- ## Motivation pocketfft currently reports all errors via C++ exceptions (invalid arguments, allocation failures, internal consistency checks). This makes the header unusable in projects that are compiled without exception handling, e.g. with GCC/Clang `-fno-exceptions` — a common setting in embedded, safety-focused and performance-oriented code bases. With `-fno-exceptions` the header fails to compile at every `throw` site: ``` pocketfft_hdronly.h: In function 'void* pocketfft::detail::aligned_alloc(...)': error: exception handling disabled, use '-fexceptions' to enable ``` and additionally at the `catch` clauses in the thread pool (GCC rejects `catch` under `-fno-exceptions` even with an empty body). The only workaround today is to sed-patch the vendored header in every downstream project. This PR adds an opt-in upstream option instead. ## What the PR adds A new configuration macro `POCKETFFT_NO_EXCEPTIONS`, following the existing `POCKETFFT_NO_VECTORS` / `POCKETFFT_NO_MULTITHREADING` naming convention: ```cpp #define POCKETFFT_NO_EXCEPTIONS // optionally + POCKETFFT_NO_MULTITHREADING #include "pocketfft_hdronly.h" // build with -fno-exceptions as usual ``` Semantics: error conditions that would normally throw an exception instead print a diagnostic to stderr and call `abort()`: ``` pocketfft: bad axis number Aborted (core dumped) ``` ## Design - Every `throw std::...;` site (26 of them) is wrapped in a `POCKETFFT_THROW(exception)` macro: - **undefined (default):** expands to `throw exception` — behavior is 100% unchanged, exceptions are still catchable as before; - **defined:** calls `POCKETFFT_NAMESPACE::detail::handle_fatal_error(e)`, which prints `pocketfft: ` to stderr and `std::abort()`. - The two `try/catch` regions of the thread pool are `#if`-guarded, since `catch` itself is a compile error under `-fno-exceptions`: - `thread_pool::create_threads()`: without exception handling the worker thread is created directly — a failing `std::thread` constructor already results in `std::terminate()` per the standard, so nothing is lost; - `threading::thread_map()`: the worker lambda simply calls `f()` without the exception-capture plumbing. - Why `abort()`: all error sites are unrecoverable (programmer errors such as invalid size/stride/axis, or OOM). In a no-exception program there is no meaningful recovery path, and abort-with-diagnostic is the customary behavior. A user-configurable fatal handler could be a follow-up if desired. ## Verification `pocketfft_demo.cc` was built and run with GCC 15 and Clang (latest) in the following configurations, with identical numerical output in all of them: 1. default (exceptions enabled) — unchanged behavior; additionally verified that an out-of-range axis still throws a catchable `std::invalid_argument`; 2. `-DPOCKETFFT_NO_EXCEPTIONS` with exceptions enabled (mixed mode); 3. `-fno-exceptions -DPOCKETFFT_NO_EXCEPTIONS -DPOCKETFFT_NO_MULTITHREADING`; 4. `-fno-exceptions -DPOCKETFFT_NO_EXCEPTIONS` (thread pool compiled in). Error-path check: with the macro defined (configurations 2–4), requesting a transform with an out-of-range axis prints `pocketfft: bad axis number` and aborts with SIGABRT. Multithreaded check: with the macro defined and the thread pool enabled, a 1024-point transform with `nthreads=4` is bit-identical to the single-threaded reference, across repeated pool runs. ## Files changed - `pocketfft_hdronly.h` — the option (macro + `handle_fatal_error`, guarded thread pool code); - `README.md` — documentation entry in "Configuration options". ## Note for downstream users This is what allows downstream projects that vendor the header and build with `-fno-exceptions` (e.g. the PIP library) to drop their local header patches and/or move to a current upstream release.