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
56 changes: 21 additions & 35 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
# 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 modernization status and the next high-value task.

## Context

Expand All @@ -14,47 +11,36 @@ reasons it still reaches Boost through a set of **compatibility shims** in `ql/`
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 migration is **complete**. All `ext::optional` / `ext::nullopt`
usages across ~79 files have been replaced with `std::optional` / `std::nullopt`.
The `QL_USE_STD_OPTIONAL` build flag has been removed from all config files, and
`ql/optional.hpp` is now a tombstoned deprecated header (like `functional.hpp` and
`tuple.hpp`).

- `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 the highest-priority migration because:
- `ql/optional.hpp` carried a `#error` on Boost ≥ 1.91 due to silent
pricing-correctness breakage.
- `optional` appeared on **public, price-affecting APIs**
(`Settings::includeTodaysCashFlows_`, `Event::hasOccurred`).

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 task: drop the Boost `any` shim, then `shared_ptr`

## How to execute it (and how to prove nothing changed)
The next shim to remove is `ext::any` → `std::any`, followed by the much larger
`shared_ptr` shim (~1,270 files). Apply the same methodology proven with `optional`:

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`,
1. **Map the blast radius** and present it (counts, affected layers, safe order).
2. **Migrate mechanically** in dependency order (bottom-up).
3. **Remove the now-dead `QL_USE_STD_*` flag** 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 `#include`.
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 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
90 changes: 35 additions & 55 deletions Docs/pages/modernization.docs
Original file line number Diff line number Diff line change
Expand Up @@ -25,59 +25,40 @@
\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 status of each
migration.

\section modernization_completed Completed migrations

\subsection modernization_optional optional (completed)

The \c ext::optional / \c ext::nullopt shim has been fully migrated to
\c std::optional / \c std::nullopt. This was the highest-priority migration
because \c ql/optional.hpp contained an explicit <tt>\#error</tt> guard on
Boost &ge; 1.91 due to silent pricing-correctness breakage, and the type
appeared on public, price-affecting APIs (\c Settings::includeTodaysCashFlows_,
\c Event::hasOccurred).

What was done:
- All \c ext::optional usages (79 files) replaced with \c std::optional.
- All \c ext::nullopt usages replaced with \c std::nullopt.
- All <tt>\#include &lt;ql/optional.hpp&gt;</tt> replaced with
<tt>\#include &lt;optional&gt;</tt>.
- \c ql/optional.hpp reduced to a thin deprecated
<tt>\#include &lt;optional&gt;</tt> tombstone.
- The \c QL_USE_STD_OPTIONAL build flag removed 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.
- Full regression suite passes with unchanged pricing results.

The \c functional and \c tuple shims were previously migrated and their
headers are already tombstoned.

\section modernization_next Next migrations

Recommended order for remaining shims: \c any, then \c shared_ptr (the
largest at around 1,270 files).

\section modernization_proof Proving pricing behavior is unchanged

Expand All @@ -89,8 +70,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 leftover shim 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
4 changes: 2 additions & 2 deletions ql/cashflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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 +40,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