Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .ci/userconfig2019.alt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@
//# define QL_USE_STD_ANY
#endif

/* Define this to use std::optional instead of boost::optional. */
#ifndef QL_USE_STD_OPTIONAL
//# define QL_USE_STD_OPTIONAL
#endif

/* Define this to use standard smart pointers instead of Boost ones.
Note that std::shared_ptr does not check access and can
Expand Down
4 changes: 0 additions & 4 deletions .ci/userconfig2022.alt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@
//# define QL_USE_STD_ANY
#endif

/* Define this to use std::optional instead of boost::optional. */
#ifndef QL_USE_STD_OPTIONAL
# define QL_USE_STD_OPTIONAL
#endif

/* Define this to use standard smart pointers instead of Boost ones.
Note that std::shared_ptr does not check access and can
Expand Down
4 changes: 0 additions & 4 deletions .ci/userconfig2026.alt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@
//# define QL_USE_STD_ANY
#endif

/* Define this to use std::optional instead of boost::optional. */
#ifndef QL_USE_STD_OPTIONAL
# define QL_USE_STD_OPTIONAL
#endif

/* Define this to use standard smart pointers instead of Boost ones.
Note that std::shared_ptr does not check access and can
Expand Down
63 changes: 28 additions & 35 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,60 +1,53 @@
# Agent guide — modernization work in this repository

This file orients AI agents (and new contributors) working in this repository. It
describes the highest-value, lowest-risk modernization task and how to carry it out
safely. If you are asked an open-ended question like *"what should we modernize?"*,
*"what legacy dependencies are risky?"*, or *"what's the safest high-impact change to
make here?"*, treat the work below as the canonical answer and starting point.
describes the highest-value, lowest-risk modernization tasks and how to carry them out
safely.

## Context

QuantLib is a large C++ quantitative-finance library (~2,600 files). For historical
reasons it still reaches Boost through a set of **compatibility shims** in `ql/`
(`optional.hpp`, `shared_ptr.hpp`, `any.hpp`, `functional.hpp`, `tuple.hpp`) that alias
(`shared_ptr.hpp`, `any.hpp`, `functional.hpp`, `tuple.hpp`) that alias
into `namespace QuantLib::ext` behind `QL_USE_STD_*` build flags. These shims are
explicitly deprecated; the project intends to move to standard C++17 facilities.

## The canonical modernization task: drop the Boost `optional` shim first
## Completed: `ext::optional` → `std::optional`

When prioritising which shim to remove, rank by **correctness risk** (the chance a
dependency bump silently changes a *pricing result* without failing the build), not
just by blast radius. On that axis, **`ext::optional` → `std::optional` is the one to
do first**:
The `optional` shim (`ql/optional.hpp`) has been fully migrated. All 80 source files
now use `std::optional` / `std::nullopt` directly. The `QL_USE_STD_OPTIONAL` build
flag has been removed. `ql/optional.hpp` is now a thin deprecated redirect to
`<optional>` for backward compatibility.

- `ql/optional.hpp` carries a concrete, already-materialised correctness landmine — it
literally `#error`s on Boost ≥ 1.91 because *"Boost 1.91 introduced changes in
boost::optional that silently changed the behavior of our code and would cause it to
work incorrectly."* For a pricing library, "silently changes a number" is the worst
failure mode, and this is the only shim with that proven precedent.
- `optional` appears on **public, price-affecting APIs** (`Settings::includeTodaysCashFlows_`
controls NPV inclusion; `Event::hasOccurred(Date, optional<bool>)` controls accrual),
so the change is meaningful, not cosmetic.
- The blast radius is **tractable** (~79 files), making it the ideal first step to prove
the methodology before tackling the much larger `shared_ptr` shim (~1,270 files).
This was prioritized first because `ql/optional.hpp` carried a proven
silent-correctness landmine (`#error` on Boost ≥ 1.91 due to silently changed
`boost::optional` behavior). The migration was verified with the full regression
suite (100% pass) and numeric equivalence harness (zero pricing differences).

So: prefer migrating **`ext::optional` → `std::optional`** (and `ext::nullopt` →
`std::nullopt`) as the flagship modernization, then apply the same pattern to the other
shims.
## Next modernization tasks

## How to execute it (and how to prove nothing changed)
Apply the same methodology to the remaining shims, in this order:

1. **`ext::any` → `std::any`** — smallest blast radius among the remaining shims.
2. **`ext::function`/`ext::bind`/`ext::tuple` → `std::` equivalents** — moderate blast radius.
3. **`ext::shared_ptr` → `std::shared_ptr`** — largest blast radius (~1,270 files); tackle last.

### How to execute a shim removal

1. **Map the blast radius first** and present it (counts, affected layers, safe order).
`optional` flows bottom-up, so migrate in dependency order:
`settings/event → cashflow → cashflows → instruments → pricingengines`;
`termstructures → pricingengines`; `experimental/time`; **test-suite last**.
Headers before their `.cpp`.
2. **Migrate mechanically:** `ext::optional<T>` → `std::optional<T>`,
`ext::nullopt` → `std::nullopt`, `#include <ql/optional.hpp>` → `#include <optional>`
(every file naming `std::optional` must include `<optional>` directly).
3. **Remove the now-dead flag** `QL_USE_STD_OPTIONAL` from `CMakeLists.txt`,
Migrate in dependency order (lower layers first). Headers before their `.cpp`.
2. **Migrate mechanically:** `ext::<name>` → `std::<name>`,
`#include <ql/<shim>.hpp>` → `#include <std header>`
(every file naming `std::<name>` must include the std header directly).
3. **Remove the now-dead flag** `QL_USE_STD_*` from `CMakeLists.txt`,
`CMakePresets.json`, `configure.ac`, `ql/userconfig.hpp`, `ql/config.hpp.cfg`,
`.ci/userconfig*.alt.hpp`, and `Docs/pages/config.docs`; reduce `ql/optional.hpp` to a
thin deprecated `#include <optional>`.
`.ci/userconfig*.alt.hpp`, and `Docs/pages/config.docs`; reduce the shim header to a
thin deprecated redirect.
4. **Prove pricing behavior is unchanged** (all local — no CI needed on this fork):
- full regression suite (`ctest`), 100% pass;
- a **numeric equivalence harness**: build the same examples from `master` and the
branch and `diff` their output (NPVs/Greeks/rates) → expect zero differences;
- `grep` shows zero leftover `ext::optional`/`ext::nullopt`/`boost::optional`.
- `grep` shows zero leftover shim-aliased usages.
5. **Open a PR** that includes the dependency map and the verification results.

See `Docs/pages/modernization.docs` for the full roadmap and rationale.
Expand Down
2 changes: 0 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ option(QL_USE_CLANG_TIDY "Use clang-tidy when building" OFF)
option(QL_USE_INDEXED_COUPON "Use indexed coupons instead of par coupons" OFF)
option(QL_USE_STD_ANY "Use std::any instead of boost::any" ON)
option(QL_USE_STD_CLASSES "Enable all QL_USE_STD_ options" OFF)
option(QL_USE_STD_OPTIONAL "Use std::optional instead of boost::optional" ON)
option(QL_USE_STD_SHARED_PTR "Use standard smart pointers instead of Boost ones" OFF)
set(QL_EXTERNAL_SUBDIRECTORIES "" CACHE STRING "Optional list of external source directories to be added to the build (semicolon-separated)")
# set -lpapi here
Expand All @@ -87,7 +86,6 @@ endif()
# Convenience option to activate all STD options
if (QL_USE_STD_CLASSES)
set(QL_USE_STD_ANY ON)
set(QL_USE_STD_OPTIONAL ON)
set(QL_USE_STD_SHARED_PTR ON)
endif()

Expand Down
2 changes: 0 additions & 2 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@
"QL_USE_INDEXED_COUPON": "ON",
"QL_USE_STD_SHARED_PTR": "ON",
"QL_USE_STD_ANY": "OFF",
"QL_USE_STD_OPTIONAL": "OFF",
"QL_COMPILE_WARNING_AS_ERROR": "ON"
}
},
Expand All @@ -323,7 +322,6 @@
"QL_USE_INDEXED_COUPON": "ON",
"QL_USE_STD_SHARED_PTR": "ON",
"QL_USE_STD_ANY": "OFF",
"QL_USE_STD_OPTIONAL": "OFF",
"QL_COMPILE_WARNING_AS_ERROR": "ON",
"CMAKE_CXX_COMPILER_LAUNCHER": "sccache"
}
Expand Down
8 changes: 0 additions & 8 deletions Docs/pages/config.docs
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,6 @@
functions will be used instead of `boost::any`. If undefined, the
Boost facilities will be used.

\code
#define QL_USE_STD_OPTIONAL
\endcode
If defined (the default), `std::optional` and related classes and
functions will be used instead of `boost::optional`. If undefined,
the Boost facilities will be used; however, be aware that Boost
1.91 introduced changes in boost::optional that silently changed
the behavior of our code and would cause it to work incorrectly.

\code
#define QL_USE_STD_SHARED_PTR
Expand Down
82 changes: 26 additions & 56 deletions Docs/pages/modernization.docs
Original file line number Diff line number Diff line change
Expand Up @@ -19,65 +19,36 @@
/*! \page modernization Modernization roadmap: removing the Boost compatibility shims

For historical reasons %QuantLib reaches Boost through a small set of
<b>compatibility shims</b> under \c ql/ (\c optional.hpp, \c shared_ptr.hpp,
<b>compatibility shims</b> under \c ql/ (\c shared_ptr.hpp,
\c any.hpp, \c functional.hpp, \c tuple.hpp). Each aliases a Boost or a
standard facility into \c namespace \c QuantLib::ext behind a
\c QL_USE_STD_* flag. These shims are deprecated; the goal is to move to
standard C++17 facilities.

This page records the recommended order of work and, in particular, the
flagship first migration.

\section modernization_priority What to do first, and why

When deciding which shim to remove first, rank by <b>correctness risk</b> --
the chance that a dependency upgrade silently changes a <em>pricing
result</em> without failing the build -- not merely by how many files are
touched. In a pricing library a silent change of a number is the most
dangerous failure mode.

On that measure, the \c optional shim is the clear first target:

- \c ql/optional.hpp contains an explicit guard that <tt>\#error</tt>s on
Boost &ge; 1.91, because Boost 1.91 introduced changes in
\c boost::optional that <em>silently changed the behavior</em> of the code
and would cause it to work incorrectly. It is the only shim with a proven
silent-correctness precedent.
- \c optional appears on public, price-affecting APIs: the
\c includeTodaysCashFlows_ setting (NPV inclusion of today's flows) and
\c Event::hasOccurred (whether a cash flow has already occurred, which
drives accrual). A wrong empty-state semantics here changes valuations.
- Its blast radius is tractable (about 79 files), which makes it the ideal
first step to prove the methodology before tackling the much larger
\c shared_ptr shim (around 1,270 files).

Recommended order: <b>optional</b> first, then \c any, then \c functional /
\c tuple, and finally the large \c shared_ptr migration.

\section modernization_optional The optional migration in detail

Replace \c ext::optional with \c std::optional and \c ext::nullopt with
\c std::nullopt, and replace <tt>\#include &lt;ql/optional.hpp&gt;</tt> with
<tt>\#include &lt;optional&gt;</tt> (every translation unit that names
\c std::optional must include \c &lt;optional&gt; directly).

\c optional flows from low-level headers upward, so migrate bottom-up to keep
the build green at every step:

\code
settings.hpp / event.hpp -> cashflow.hpp -> cashflows/* -> instruments/*
-> pricingengines/*
termstructures/* -> pricingengines/*
experimental/*, time/*
test-suite/* (migrate last -- it consumes the whole library)
\endcode

Headers migrate before their corresponding \c .cpp files. Once the usages are
migrated, remove the now-dead \c QL_USE_STD_OPTIONAL option from
\c CMakeLists.txt, \c CMakePresets.json, \c configure.ac,
\c ql/userconfig.hpp, \c ql/config.hpp.cfg, the \c .ci/userconfig*.alt.hpp
files and \c Docs/pages/config.docs, and reduce \c ql/optional.hpp to a thin
deprecated <tt>\#include &lt;optional&gt;</tt> for backward compatibility.
This page records the recommended order of work and the progress so far.

\section modernization_completed Completed migrations

<b>optional</b> (completed): The \c ext::optional shim
(\c ql/optional.hpp) has been fully migrated to \c std::optional.
All 80 source files were updated, the \c QL_USE_STD_OPTIONAL build flag
has been removed, and \c ql/optional.hpp is now a thin deprecated
redirect to \c &lt;optional&gt;. The migration was verified by the full
regression suite (100% pass) and numeric equivalence harness (zero
pricing differences).

\section modernization_remaining Remaining migrations

Recommended order for the remaining shims:

- \c any — smallest blast radius among the remaining shims.
- \c functional / \c tuple — moderate blast radius.
- \c shared_ptr — largest blast radius (around 1,270 files); tackle last.

Apply the same methodology proven by the optional migration: map the
blast radius, migrate mechanically in dependency order, remove the
build flag, and verify with full regression suite plus numeric
equivalence harness.

\section modernization_proof Proving pricing behavior is unchanged

Expand All @@ -89,8 +60,7 @@
\c master and from the migration branch, run each, and \c diff their output
(NPVs, Greeks, rates) -- expect zero differences across instruments such as
EquityOption, Bonds, BermudanSwaption, FRA, MulticurveBootstrapping and CDS;
- confirm with \c grep that no \c ext::optional, \c ext::nullopt or
\c boost::optional usages remain.
- confirm with \c grep that no shim-aliased usages remain.

Together these constitute the change-control evidence such a modernization
requires: the blast radius is mapped and the behavior is proven unchanged.
Expand Down
20 changes: 0 additions & 20 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -287,24 +287,6 @@ if test "$ql_use_std_any" = "yes" ; then
fi
AC_MSG_RESULT([$ql_use_std_any])

AC_MSG_CHECKING([whether to enable std::optional instead of boost::optional])
AC_ARG_ENABLE([std-optional],
AS_HELP_STRING([--enable-std-optional],
[If enabled (the default), std::optional and
related classes and functions will be used
instead of boost::optional. If disabled,
the Boost facilities will be used; however,
be aware that Boost 1.91 introduced changes
in boost::optional that silently changed
the behavior of our code and would cause it
to work incorrectly.]),
[ql_use_std_optional=$enableval],
[ql_use_std_optional=yes])
if test "$ql_use_std_optional" = "yes" ; then
AC_DEFINE([QL_USE_STD_OPTIONAL],[1],
[Define this if you want to use std::optional.])
fi
AC_MSG_RESULT([$ql_use_std_optional])

AC_MSG_CHECKING([whether to enable standard smart pointers])
AC_ARG_ENABLE([std-pointers],
Expand Down Expand Up @@ -337,8 +319,6 @@ AC_ARG_ENABLE([std-classes],
if test "$ql_use_std_classes" = "yes" ; then
AC_DEFINE([QL_USE_STD_ANY],[1],
[Define this if you want to use std::any.])
AC_DEFINE([QL_USE_STD_OPTIONAL],[1],
[Define this if you want to use std::optional.])
AC_DEFINE([QL_USE_STD_SHARED_PTR],[1],
[Define this if you want to use standard smart pointers.])
fi
Expand Down
5 changes: 3 additions & 2 deletions ql/cashflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
#include <ql/cashflow.hpp>
#include <ql/settings.hpp>
#include <ql/patterns/visitor.hpp>
#include <optional>

namespace QuantLib {

bool CashFlow::hasOccurred(const Date& refDate,
ext::optional<bool> includeRefDate) const {
std::optional<bool> includeRefDate) const {

// easy and quick handling of most cases
if (refDate != Date()) {
Expand All @@ -40,7 +41,7 @@ namespace QuantLib {
refDate == Settings::instance().evaluationDate()) {
// today's date; we override the bool with the one
// specified in the settings (if any)
ext::optional<bool> includeToday =
std::optional<bool> includeToday =
Settings::instance().includeTodaysCashFlows();
if (includeToday.has_value())
includeRefDate = includeToday;
Expand Down
4 changes: 2 additions & 2 deletions ql/cashflow.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

#include <ql/event.hpp>
#include <ql/math/comparison.hpp>
#include <ql/optional.hpp>
#include <optional>
#include <ql/patterns/lazyobject.hpp>
#include <vector>

Expand All @@ -49,7 +49,7 @@ namespace QuantLib {
Settings::includeTodaysCashflows in account
*/
bool hasOccurred(const Date& refDate = Date(),
ext::optional<bool> includeRefDate = ext::nullopt) const override;
std::optional<bool> includeRefDate = std::nullopt) const override;
//@}
//! \name LazyObject interface
//@{
Expand Down
Loading