Chapter 1. An Overview of C++
This chapter presents an overview of C++, beginning with a brief history—including its early popularity in quantitative finance—and the start of its modern era marked by major enhancements in 2011, released as C++11. Prior to C++11, the last release was in 2003 (C++03), which was an update mainly addressing bug fixes following the first C++ release that was compliant with the International Organization of Standardization (ISO) in 1998 (C++98). The modernization that began with C++11 has continued with further innovations added to the core language, and its companion, the C++ Standard Library, every three years, bringing us to the current C++23 version.
You will also be introduced to useful new language features that will be used in subsequent chapters, as well as convenient math-related updates to the Standard Library. Finally, we will look at common class, function, and variable naming conventions, and point out those that will be used throughout this book.
C++ and Quantitative Finance
C++ started its rapid growth in the financial sector around the mid-1990s. Many of us who were in the industry around this time had been raised on Fortran, which was used for writing numerical routines and scientific applications. While Fortran and its supporting libraries (BLAS, LAPACK, IMSL) were well-developed in terms of mathematical and linear algebra support, the language lacked support for object-oriented programming, the inclusion of which was considered C++’s big advantage among financial developers at the time. Of course, C++ supports more than object-oriented programming, as is discussed later in this chapter.
Financial modeling in the abstract naturally comprises components that interact with one another. For example, pricing even a simple derivatives contract based on foreign exchange and interest rates would typically require the following:
-
The yield curve for each currency
-
A market rate feed of live foreign exchange rate quotes
-
Volatility curves and/or surfaces for option pricing and calculating risk measures
-
A set of derivative pricing methods, such as closed-form solutions and numerical approximations
Each of these components can be represented by an object, and C++ provides the means for creating these objects and managing their relationships to one another.
Banks and other financial institutions also needed a way to generate risk reports at both a regional and global scale. This was a significant challenge for companies with trading operations spread across the major financial centers around the world such as New York, London, and Tokyo. At the start of each trading day, risk reporting was required for a firm’s headquarters in, say, New York that took into account the portfolios maintained both locally and globally. This could be a computationally intensive task, but the performance of C++ made it possible and was yet another significant factor in its early adoption in the financial industry.
Following the turn of the century, newer object-oriented languages, such as Java and C#, made software development a somewhat easier and faster process, while more-efficient processors became less expensive. However, the same features in these languages that enabled quicker deployment (such as built-in managed memory, garbage collection, and intermediate compilation) could also introduce overhead in terms of runtime performance. Management decisions on which language to adopt often came down to a trade-off between more-rapid development and runtime efficiency. However, even if one of these language alternatives were employed, computationally intensive pricing models and risk calculations were—and still are—often delegated to existing C++ libraries and called via an interface. It should also be noted that C++ offers certain compile-time optimizations that are not available in these other programming languages.
C++11: The Modern Era Is Born
In 2011, the ISO C++ Committee (usually just called the Committee) released a substantial revision that addressed long-needed modernization. In particular, C++11 provided these welcome abstractions that are immediately useful to quantitative developers:
-
Random number generation from a variety of probability distributions
-
Lambda expressions encapsulating mathematical functions that can also be passed as arguments to other functions
-
Basic task-based concurrency that can parallelize computations without the need for manual thread management
-
Smart pointers that can help prevent memory-related program crashes and undefined behavior
These topics and more are discussed in the chapters ahead.
Note
In this chapter and throughout the book, you will sometimes see the term undefined behavior. This is referenced throughout the C++ Standard as well, and it means behavior for which no guarantees are provided. In common parlance, this means the code is not “playing by the rules,” and that the program is broken. So, if your code is at risk of causing undefined behavior, the simple advice is “do not do that.”
Following C++11, new releases with more and more modern features addressing the demands of financial and data science industries are being rolled out on a three-year cadence, with the most recent release being C++23. This book primarily covers developments through C++20, but it will also discuss some new items in C++23, as well as a few proposed coming attractions in C++26 that should be of interest to financial developers.
Proprietary and high-frequency trading firms have been at the forefront of adopting C++11 and later standards, because the speed of acting on market and trading book signals in statistical strategies can mean a profound difference in profit and loss. Modern C++ is also in keen demand for implementing computationally intensive derivative pricing models utilized by traders and risk managers at investment banks and hedge funds.
For more coverage of the broader history and evolution of C++ into the modern era, C++ Today: The Beast is Back by Jon Kalb and Gašper Ažman (O’Reilly, 2015) is highly recommended. It should also be noted that with the publication of, and more attention to, the ISO C++ Core Guidelines and ISO C++ Coding Standards, C++ development can now be more reliable and efficient than in years past. The Core Guidelines in particular are referenced with some frequency throughout this book.
Open Source Mathematical Libraries
Another welcome development over the past decade has been the proliferation of robust open source mathematical libraries written in standard C++ that therefore do not require the time-consuming C-language interface gymnastics of the past. Primary among these are the Boost libraries, the Eigen and Armadillo linear algebra libraries, and machine learning libraries such as TensorFlow and PyTorch. We will discuss Boost and Eigen further as the book proceeds.
Some Myths about C++
Here are some of the more infamous false beliefs that have been perpetuated about C++:
- Knowledge of C is necessary for learning C++.
-
While the C++ Standard retains most of the C language, it is entirely possible to learn C++ without knowledge of C, as you will see in the material that follows. Clinging to C style can, in fact, hinder learning the powerful abstractions and potential benefits of C++.
- C++ is too difficult.
-
OK, yes, this has some truth, as C++ is undoubtedly a rich language that provides plenty of the proverbial rope with which to hang ourselves, and indeed it can at times be the source of nontrivial frustration. However, by leveraging modern features of the language, it is entirely possible to become productive as a quantitative financial developer in C++ more quickly compared to relying on legacy C++03 methods.
- Memory leaks are always a problem in C++.
-
With smart pointers available since C++11—one of the more prominent modern features—most code should not need to play with raw pointers or memory allocation. Along with other Standard Library features such as standard algorithms (covered in Chapter 5), this no longer needs to be an issue in most financial-modeling implementations.
Compiled Versus Interpreted Code
C++ is a compiled language: commands typed into a file by us mere mortals are first translated into a set of binary instructions, or machine code, that a computer processor will understand. This is in contrast to nontyped and interpreted quantitative languages such as Python, R, and MATLAB, where each line of code must be reprocessed each time it is executed, thus slowing execution time for larger applications, especially those with heavy reliance upon iterative (looping) statements.
This is by no means a knock on these languages. Their power is evident in their popularity for rapid implementations and visualizations of models arising in quantitative fields such as finance, data science, and biosciences, where their available mathematical and statistical functions are often already compiled in C, C++, or Fortran. However, financial programmers may be well aware of models that would require days to run in an interpreted language, but only a matter of minutes or seconds when reimplemented in C++.
An effective approach is to use interpreted mathematical languages with C++ in a complementary fashion. Computationally intensive models code could be written in a C++ library and then called either interactively or from an application in Python or R, for example. C++ efficiently takes care of the number crunching, while the results can be used inside powerful plotting and other visualization tools in Python and R that are not available in the C++ Standard Library.
Another advantage of organizing financial models in a C++ library is the code is written once and maintained in a single repository. This way, a common set of code can be deployed across many departments, divisions, and even international boundaries, and called via interfaces from applications written in different frontend languages. This helps ensure consistent numerical results throughout the organization, which can be particularly advantageous for regulatory compliance purposes.
Popular open source C++ integration packages are available for both R and Python (namely, Rcpp and pybind11, respectively). MATLAB also provides options for C++ interfaces, although nontrivial license fees can be required for their add-on features.
The Components of C++
Standard C++ releases at a high level consist of two components: language features and the C++ Standard Library. Together, the Standard C++ language and the Standard Library are commonly referred to as the Standard.
C++ Language Features
C++ language features mostly overlap with the following essential operators and constructs you would find in other programming languages:
-
Fundamental integer and floating-point numerical types
-
Conditional branching:
if
/else
statements andswitch
/case
statements -
Iterative constructs:
for
loops andwhile
loops -
Standard mathematical and logical operators for numerical types: addition, subtraction, multiplication, division, modulus, and inequalities
C++ language features support at least four of the major programming paradigms: procedural programming, object-oriented programming, generic programming, and functional programming. A short list of specific related features are, respectively, as follows:
-
Free functions, also referred to as nonmember functions (of a class)
-
Classes and inheritance
-
Templates
-
Lambda expressions (since C++11)
Each is utilized throughout the book, beginning with this chapter.
Finally, the C++ language provides a plethora of fundamental numerical and logical types. Those that we will primarily use are as follows:
-
double
(double precision) for floating-point values -
int
for positive and negative integers -
unsigned int
(or alternatively justunsigned
) for nonnegative integers -
bool
for boolean representation (true
orfalse
)
Ranges for each of the numerical types can vary across platforms, but on modern compilers, the types noted here are mostly sufficient for modern financial applications. You can find a comprehensive guide that provides these ranges and details on cppreference.com. This website is an essential resource for any C++ developer.
The C++ Standard Library
A software library is essentially a set of functions and classes that are called by an application or system. Library development—both open source and commercial—now dominates modern C++ development compared to standalone applications that were more popular in previous decades. Subsequent chapters cover some of the popular open source options that are useful for computational work. The most important C++ library, however, is the Standard Library, which is shipped with most modern compilers.
The C++ Standard Library “enable(s) programmers to use general components and a higher level of abstraction without losing portability rather than having to develop all code from scratch.”1 Up through C++23, highly useful library features for financial modeling include the following:
-
Single-dimension container classes, particularly the dynamically resizeable
vector
class -
A wide set of standard algorithms that operate on these containers, such as sorting, searching, and efficiently applying functions to a range of elements in a container
-
Standard real-valued mathematical functions such as square root, exponential, and trigonometric functions
-
Complex numbers and related arithmetic operations
-
Random number generation from a set of standard probability distributions
-
Task-based concurrency that can provide return values from functions run in parallel
-
Smart pointers that abstract away the dangers associated with memory allocation and management
-
A
string
class to store and manage character data
Use of Standard Library components requires the programmer to explicitly import them into the code, as they reside in a separate library rather than within the core language. The idea is similar to importing a NumPy array into a Python program or loading an external package of functions into an R script. In C++, this is a two-step process, starting with including the header files (with the #include
preprocessor directive) containing the Standard Library functions and classes we wish to use, and then scoping these functions with the Standard Library namespace name, std
, often pronounced as “stood” by C++ developers.
As a quick first example, let’s create a vector
of int
values and a string
object, and output them to the console from a simple executable program inside the main()
function:
#include <vector> // vector class #include <string> // string class #include <iostream> // cout int main() { std::vector<int> x{1, 2, 3}; std::string s{"This is a vector:"}; std::cout << s << x[0] << ", " << x[1] << ", " << x[2] << "\n"; }
Note that the Standard Library classes vector
and string
, and the console output cout
, are scoped with the Standard Library std
namespace. If you wish to save yourself typing std::
, you can employ using
statements, preferably within individual function scopes, although placed at the top of a file can be acceptable in certain limited situations, such as writing small sets of test functions:
#include <vector> // vector class #include <string> // string class #include <iostream> // cout int main() { using std::vector, std::string, std::cout; vector<int> v{1, 2, 3}; string s{"This is a vector:"}; cout << s << v[0] << ", " << v[1] << ", " << v[2] << "\n"; }
The output is not surprisingly as follows:
This is a vector: 1, 2, 3
The using
statement is required only once, with the three Standard Library components following on the same line. This is a newer feature as of C++17. For examples in this book, it will often be assumed that using
statements have been applied to commonly used Standard Library classes and functions such as vector
, string
, and cout
. Also, it should be noted that cout
is generally not used in production code. We will use it as a placeholder where a result in reality would more likely be passed to a GUI or database interface, or another section of code.
Warning
Importing the std
namespace into the global namespace with
using namespace std;
is sometimes found in code to replace the individual using
statements or explicit scoping with the std
namespace. However, this is not considered good practice, as it can result in naming clashes at compile time. You can find more details in the ISO C++ Coding Standards FAQ entry, “Should I use using namespace std
in my code?”.
Uniform initialization (also called braced initialization), used in some of the preceding examples, was also introduced in C++11:
vector<int> v{1, 2, 3}; string s{"This is a vector:"};
This is a useful feature in general that is discussed in “Uniform Initialization”. For further convenience, you will see later that the template parameter can be dropped in certain cases, using another newer feature called class template argument deduction, or CTAD for short.
To close out this introductory discussion of the Standard Library, one more type to be aware of is std::size_t
(size type). Rather than being part of the core language, it is contained in the Standard Library and “is defined to be an unsigned integer with enough bytes to represent the size of any type.”2 It is not as much a distinct type as it is an alias for one of the existing unsigned integral types, so it might be unsigned int
on some platforms and unsigned long
on others. Per its definition, it will always be at least as large as the return type for the size of a vector
.
The size type of a vector
is also a platform-dependent unsigned integral type, which in some cases might be the same as size_t
but not necessarily so. However, because size_t
will be at least as large, the value returned from the size()
member function is often just implicitly converted:
#include <vector> #include <cstdlib> // std::size_t . . . std::vector<int> v{1, 2, 3}; std::size_t v_size = v.size();
As you will see soon, however, with modern features of C++ such as auto
and range-based for
loops, this issue has become more of an artifact.
Some New Language Features Since C++11
This section covers some useful features and syntax introduced since C++11 (inclusive). Although it has been over 10 years since C++11 hit the scene, it unfortunately is still not covered in many university and quantitative finance programs, let alone later releases such as C++17.
The auto Keyword
C++11 introduced the auto
keyword that can automatically deduce a variable or object type. Here are simple examples:
auto k = 1; // int auto x = 419.53; // double
In this case, k
is deduced as an int
type, and x
a double
type.
Programmers have varied opinions on the use of auto
, but many still prefer to explicitly state fundamental types such as int
and double
to avoid ambiguity. This is the style followed in this book.
The auto
keyword becomes more useful when the return type is reasonably obvious from the context. As you will see in Chapter 3, a unique or a shared pointer can be created with the Standard Library function make_unique<T>(.)
or make_shared<T>(.)
:
auto call_payoff = std::make_unique<CallPayoff>(75.0); auto mkt_data = std::make_shared<LiveMktData>("CattleFutures");
In the first case, make_unique<CallPayoff>
makes it fairly obvious that a unique pointer to a CallPayoff
object is being created (with a strike of 75). In the second, make_shared<LiveMktData>
says it is creating a shared pointer to a LiveMktData
object (providing cattle futures prices). These are sufficiently expressive and a lot easier to maintain than what would be required otherwise:
std::unique_ptr<CallPayoff> call_payoff = std::make_unique<CallPayoff>(75.0); std::shared_ptr<LiveMktData> mkt_data = std::make_shared<LiveMktData>("CattleFutures");
It is also handy if the return type is a long and nested class template type, such as from a function as follows:
std::map<std::string, std::complex<double>> map_of_complex_numbers(. . .) { // . . . std::map<std::string, std::complex<double>> map_key_string_val_complex; // . . . return map_key_string_val_complex; }
Then, instead of the pre-C++11 way
std::map<std::string, std::complex<double>> cauchys_revenge = map_of_complex_numbers(. . .);
we can more cleanly and clearly call the function and define the result by using auto
:
auto cauchys_revenge = map_of_complex_numbers(. . .);
Range-Based for Loops
Prior to C++11, iterating through a vector
would involve using the index as the counter, up to the number of its elements:
vector<double> v{1.0, 2.0, 3.0, 4.0, 5.0}; for(std::size_t i = 0; i < v.size(); ++i) { // Do something with v[i] } // For example: for (std::size_t i = 0; i < v.size(); ++i) { cout << v[i] << " "; }
Alternatively, you can also use an iterator-based for
loop, which was also available before C++11:
for (auto iter = v.begin(); iter != v.end(); ++iter) { cout << *iter << " "; }
In some cases, this form can be more convenient, as you will see in later examples. As an aside here, note that the auto
keyword means we can save ourselves from first having to explicitly specify the iterator type, such as with this:
for (std::vector<double>::iterator iter = v.begin(); iter != v.end(); ++iter) { // . . . }
Range-based for
loops, introduced in C++11, can make this code more elegant and functional. Instead of explicitly using the vector
index, a range-based for
loop simply says, “for every element x
in v
, do something with it,” similar to what you would find using Python or R:
for (double x : v) { cout << x << " "; }
Range-based for
loops can also be used in applying operations to the elements of a vector
. As an example, we could calculate the sum of the elements in the vector v
:
double sum = 0.0; for (double x : v) { sum += x; }
With this, we are done. No worries about making a mistake with the index, and the code more obviously expresses what it is doing. The C++ Core Guidelines, in fact, tell us to prefer using range-based for
loops with vector
objects, as well as with other standard containers that are discussed in Chapter 4.
The reference operator &
can also be used in range-based for
loops, similarly to functions:
for (double& x : v) { x *= x; } // vector v is now 1, 4, 9, 16, 25 vector<string> sorry_dave{"Open", "the", "pod", "bay", "doors", "HAL"}; for (const string& s : sorry_dave) { cout << s << " "; }
The output is:
Open the pod bay doors HAL
The using Keyword
Another way to ease the pain with cryptic template types prior to C++11 was to use a typedef
. Revisiting the preceding complex map example, we could define an alias, complex_map
:
typedef std::map<std::string, std::complex<double>> complex_map;
Beginning with C++11, the using
statement was extended to perform the same task, but in a more natural syntax:
using complex_map = std::map<std::string, std::complex<double>>;
The Core Guidelines recommend preferring using
over typedef
, first because of improved readability: “With using
, the new name comes first rather than being embedded somewhere in a declaration.”3 There are also considerations with respect to advanced template methods, although these are beyond the scope of this book.
Uniform Initialization
C++11 introduced uniform initialization, also called braced initialization. There are several use cases, beginning with the simple case of initializing a numeric variable:
int i{100};
This isn’t terribly interesting, as it simply replaces writing int i = 100;
. However, what if we had the following?
double x = 92.09; int k = x; // Compiles, possibly with a warning
This will compile, albeit possibly (but not guaranteed) with a warning to the effect that the decimal part of x
will be truncated, leaving k
holding 92 alone. With uniform initialization, the compiler would issue a narrowing conversion error and halt the build, thus preventing unexpected results at runtime. This is a good thing, as it’s better to catch errors at compile time rather than runtime:
int n{x}; // Compiler ERROR: narrowing conversion
An alternate equivalent form of uniform initialization is to put an equals sign between the variable name and the left brace:
int i_alt = {100}; vector<int> v = {1, 2, 3};
The particular style for using braced initialization adopted for this book, however, is braced initialization without the equals sign.
Formatting Output
A new feature in the Standard Library as of C++20 worth mentioning is a new std::format(.)
function that can format character and numerical data into a text format and return it as a string
object.
For example, suppose we have two variables u
and v
that have been assigned some values:
double u = 1.5; double v = 4.2;
If we want to output these values with the variable name labels, we could write something like the following:
cout << "u = " << u << ", v = " << v << "\n";
The output to the screen would then be as follows:
u = 1.5, v = 4.2
Chaining the chevrons together, however, can become tiresome. Instead, we can now use format(.)
as follows:
#include <format> . . . string output = std::format("\nu = {0}, v = {1}\n", u, v); cout << output;
This says to put the value held by u
in the first (zero-indexed) position after "u = "
, and then the value v
in the following position. The output is then similar to before:
u = 1.5, v = 4.2
For those who are familiar with C#, you may notice this usage is similar in form to Console.WriteLine(.)
.
Alternatively, as the format(.)
function returns a string
type, it can just be placed inside the cout
statement:
using std::format; cout << format("nu = {0}, v = {1}\n", u, v);
This gives us the following:
u = 1.5, v = 4.2
When the order is from left to right, however, the index values can be dropped, and the output results will be the same as shown in the preceding example:
cout << format("u = {}, v = {}\n", u, v);
In some cases, however, the index values are needed:
#include <cmath> // To be covered in more detail later in this chapter // . . . cout << format("u = {0}, v = {1}, sin({0}) + {1} = {2}\n", u, v, std::sin(u) + v);
The order is then preserved in the output:
u = 1.5, v = 4.2, sin(1.5) + 4.2 = 5.197495
To reiterate, console output in production code would be rare, if ever, but it can be useful as a tool when learning C++ on its own. As such, it is used throughout the book for demonstrating output results.
Going forward, we will often assume a using
statement exists so we can drop the std::
scope when applying format(.)
in code examples, similar to vector
, string
, and cout
as noted earlier.
Class Template Argument Deduction
C++17 introduced class template argument deduction (CTAD). Similar to auto
, it will deduce the type of a template parameter based on the data being initialized. So, in place of the earlier example
std::vector<int> v_01{1, 2, 3};
we could instead drop the int
template parameter to arrive at the same result:
std::vector v_02{1, 2, 3};
These examples are again trivial, using just hardcoded values, but CTAD in more realistic situations can lighten the notation and make your code more readable. You will see such examples later in the book, particularly in Chapter 8 when working with the multidimensional array view std::mdspan
, a notable new feature in C++23.
Enumerated Constants and Scoped Enumerations
Prior to C++11, enumerated constants (more commonly called enums for short) were a great means of making it clearer for us mere mortals to comprehend integer codes by representing them as named constants. Using enums could also be far more efficient for the machine to process integers rather than passing bulkier std::string
objects that take up more memory. And finally, errors caused by typos in quoted characters and stray strings could be avoided.
The C++11 Standard improved on this further with scoped enumerations (using enum class
). These remove ambiguities that can occur with overlapping integer values when using regular enum constants, while preserving the advantages.
In what follows, the motivation for preferring the more modern scoped enumerations over integer-based enums is presented.
Enumerated constants
To start with an example, we could create an enum called OptionType
that will represent the types of option deals that are allowed in a simple trading system, such as European, American, Bermudan, and Asian. The enum
name (OptionType
) is declared; then, inside the braces, the particular constant values are defined, separated by commas. By default, each will be assigned an integer value starting at zero and incremented by one (consistent with zero-based indexing in C++). The closing brace must be followed by a semicolon. In code, to illustrate, we could write the following:
enum OptionType { European, // default integer value = 0 American, // = European + 1 = 1, etc... Bermudan, // = 2 Asian // = 3 };
Just to verify each corresponding integer value, output it to the screen:
cout << " European = " << European << "\n"; cout << " American = " << American << "\n"; cout << " Bermudan = " << Bermudan << "\n"; cout << " Asian = " << Asian << "\n"; cout << " American + Asian = " << American + Asian << "\n";
Checking the output, we get this:
European = 0 American = 1 Bermudan = 2 Asian = 3 American + Asian = 4
Potential conflicts with enums
As discussed at the outset, for any enum
type, the default integer assignments start at zero and then are incremented by one for each value. Therefore, it is possible that two enumerated constants from two different types could be numerically equal. For example, suppose we define two enum
types, called Football
and Baseball
, representing the defensive positions in each sport. By default, the baseball positions start with 0 for the pitcher and are incremented by one for each in the list. The same goes for the (American) football positions, starting with defensive tackle. The integer constants are provided in the comments:
enum Baseball { Pitcher, // 0 Catcher, // 1 First_Baseman, // 2 Second_Baseman, // 3 Third_Baseman, // 4 Shortstop, // 5 Left_Field, // 6 Center_Field, // 7 Right_Field // 8 }; enum Football { Defensive_Tackle, // 0 Edge_Rusher, // 1 Defensive_End, // 2 Linebacker, // 3 Cornerback, // 4 Strong_Safety, // 5 Free_Safety // 6 };
Then, we could compare Defensive_End
and First_Baseman
:
if (Defensive_End == First_Baseman) { cout << "Defensive_End == First_Baseman\n"; } else { cout << "Defensive_End != First_Baseman\n"; }
Our result would be nonsense:
Defensive_End == First_Baseman
This is because both positions map to an integer value of 2. A quick fix, and one that was often employed prior to C++11, would be to reindex each set of enums:
enum Baseball { Pitcher = 100, Catcher, // 101 First_Baseman, // 102 . . . }; enum Football { Defensive_Tackle = 200, Edge_Rusher, // 201 Defensive_End, // 202 . . . };
Now, if we compare Defensive_End
and First_Baseman
, they will no longer be equal, because . Still, large codebases might have hundreds of enum definitions, so it would not be out of the question for an overlap to slip in and cause errors. Enum classes, introduced in C++11, eliminate this risk.
Scoped enumerations with enum classes
Using modern C++, a more robust approach obviates manually manipulating enumeration integer values. The other benefits of enums still remain, such as avoiding cryptic raw numerical codes or relying on string
objects, but the numerical conflicts like those shown in the previous examples are avoided by using a scoped enumeration, accomplished with an enum class, a new language feature in C++11. As an example, we could define bond and futures contract categories, as shown here:
enum class Bond_Type { Government, Corporate, Municipal, Convertible }; enum class Futures_Contract { Gold, Silver, Oil, Natural_Gas, Wheat, Corn };
Attempting to compare enumerators from two enum classes, such as a Corporate
bond and a Natural_Gas
futures position, will now result in a compiler error. For example, the following will not even compile:
if(Bond_Type::Corporate == Futures_Contract::Silver) { // . . . }
This works to our advantage, as again it is much better to catch an error at compile time rather than chase it at runtime. The Core Guidelines now maintain that we should prefer using enum classes rather than enumerated constants, to “minimize surprises.”4
It is still possible to cast scoped enums to integer index values if desired. For example, each of Bond::Corporate
and FuturesContract::Silver
is the second member in its respective enum class, so by default each can be cast to a value of 1
(zero-indexed), even though they are not comparable:
cout << format("Corporate Bond index: {}\n", static_cast<int>(Bond_Type::Corporate)); cout << format("Natural Gas Futures index: {}\n", static_cast<int>(Futures_Contract::Natural_Gas));
The output is shown here:
1 3
It is also possible to explicitly assign particular index values in an enum class
, if desired. For example:
// Explicitly set each index value: enum class Futures_Contract { Gold = 100, Silver = 102, Oil = 104, Natural_Gas = 106, Wheat = 108, Corn = 110 };
These integers can then be recovered by casting to int
types:
// silver_int = 102 auto silver_int = static_cast<int>(Futures_Contract::Silver); // natural_gas_int = 106 auto natural_gas_int = static_cast<int>(Futures_Contract::Natural_Gas);
But again, there is no risk of equivalence with an enumerator from another enum class
, as this is prevented by the compiler. In some cases, however, having numerical representations can be convenient, as you will see in later chapters.
Finally, enums in general, and enum classes in particular, are natural complements to switch
/case
statements. The following code outlines an example with a scoped enum:
void switch_statement_scoped_enum(Bond_Type bnd) { switch (bnd) { case Bond_Type::Government: std::cout << "Government Bond..." << "\n"; // Do stuff... break; case Bond_Type::Corporate: cout << "Corporate Bond..." << "\n"; // Do stuff... break; case Bond_Type::Municipal: cout << "Municipal Bond..." << "\n"; // Do stuff... break; case Bond_Type::Convertible: cout << "Convertible Bond..." << "\n"; // Do stuff... break; default: cout << "Unknown Bond..." << "\n"; // Check the bond type... break; } }
Lambda Expressions
A lambda expression is often referred to as an anonymous function object, a term ostensibly coined from its original design proposal back in 2006. Also known in the vernacular as a lambda function, or just a plain lambda, it can define a function (or more precisely, a functor, to be discussed in the next chapter) on the fly within the body of another function. Additionally, a lambda can be passed as an argument into another function. This last property is what makes them so powerful for use within standard algorithms, which again are covered in Chapter 5.
Lambda expressions are a welcome addition to the modern C++ arsenal. Introduced first in C++11, various enhancements were made in each subsequent release, including the most recent C++20 Standard. You can find a helpful summary of the evolution of these improvements in in Jonathan Boccara’s blog post, “The Evolution of Lambdas in C++14, C++17, and C++20”.
To begin the discussion, a lambda expression that prints out good old “Hello World!” can be written as follows inside an enclosing function:
void hello_world() { auto f = [] { std::cout << "Hello World!" << "\n"; }; f(); }
A lambda can also take in function arguments by using optional parentheses, a la a regular C++ function:
auto g = [](double x, double y) { return x + y; }; double z = g(9.2, 2.6); // z = 11.8
Note three points at this stage.
First, by default, a lambda’s return type is deduced following the same rules as those used for auto
variables; that is, their return type is the type of the expression used for the return
statement. However, we do have the option of indicating an explicit return type is an option, as follows:
auto g = [](double x, double y) -> double { return x + y; };
When the return type is readily obvious, however, the explicit type is typically dropped.
Second, be sure to place a semicolon at the end of the lambda block. When placed on a single line as in the first example, the lambda is somewhat obvious as it looks just like any other one-line C++ statement. However, the semicolon can be easily overlooked if your lambda implementation spans several lines, in which case your code will fail to compile.
Third, in cases of no function arguments, such as in the first example, the parentheses are generally optional (except, technically speaking, for special cases we need not be concerned with in this book), but the square brackets are mandatory in order to define a lambda. The square brackets in the previous examples are empty, but in general they provide the capture of the lambda expression.
The capture of a lambda expression does what it says: it captures (nonstatic) external variables, including objects, allowing them to be used inside the body of the lambda. The capture data may be taken in by value or by non-const
reference, with the latter option potentially resulting in modification.
For example, suppose we have a vector
of real numbers, u
. We want to add a constant shift value to each element and then multiply each shifted value by a scalar alpha
. We could write a lambda expression that captures u
by reference, shift
by value, and then takes alpha
in as an argument. If written this way, u
can be modified inside the lambda:
vector u{1.0, 1.5, 2.0, 2.5, 3.0, 3.5}; double shift = 0.25; auto shift_scalar_mult = [&u, shift](double alpha) { for (double& x : u) { x = alpha * (x + shift); } }; shift_scalar_mult(-1.0);
In this example, u
is modified to its new state, which is also reflected externally after the lambda is exited, because it is being captured by reference. After running this code, u
will contain the values –1.25, –1.75, –2.25, –2.75, –3.25, and –3.75.
The capture in general can contain an arbitrary number of variables, but you need to be sure not to designate a single variable by both value and reference; for example, putting
[u, &u, shift]
in the preceding capture would result in a compiler error.
Mathematical Operators, Functions, and Constants in C++
Standard mathematical operators for numerical types are available as language features in C++. Common mathematical functions (such as cosine and exponential) plus a newer set of special functions are provided in the C++ Standard Library rather than in the core language.
Standard Arithmetic Operators
Addition, subtraction, multiplication, and division of numerical types are provided in C++ with the operators +
, -
, *
, and /
, respectively, as usually found in other programming languages. Furthermore, the modulus operator, %
, is also defined for integral types (int
, unsigned
, long
, etc.). Some examples are as follows:
// integers: int i = 8; int j = 5; int k = i + 7; int v = j - 3; int u = i % j; // double precision: double x1 = 3.06; double x2 = 8.74; double x3 = 0.52; double y = x1 + x2; double z = x2 * x3;
The order and precedence of arithmetic operators are the same as found in most other programming languages, as well as middle school math for that matter:
-
Order runs from left to right:
i + j - v
Using the preceding integer values would result in 8 + 5 – 2 = 11.
-
Multiplication, division, and modulus take precedence over addition and subtraction:
x1 + y / z
Using the preceding double-precision values would result in the following:
Use parentheses to change the precedence:
(x1 + y) / z
This would yield the following:
-
Compound assignment operators are also included. For example,
x1 = x1 + x2;
can be replaced with addition assignment:
x1 += x2;
The remaining operators -
, *
, /
, and %
also have their respective versions of compound assignment operators: -=
, *=
, /=
, and %=
.
Mathematical Functions in the Standard Library
Many of the usual mathematical functions we find in other languages are provided in the C++ Standard Library and have the same or similar syntax. Table 1-1 provides a non-exhaustive list of those commonly used in computational applications; x
, y
, and z
are assumed to be double-precision variables.
C++ | Description |
---|---|
|
Cosine of |
|
Sine of |
|
Tangent of |
|
Exponential function |
|
Natural logarithm |
|
Square root of |
|
Cube root of |
|
raised to the power of |
|
Computes |
|
Computes (since C++17) |
|
Computes the error function |
A comprehensive list of common mathematical functions is available on cppreference.com.
As these are contained in the Standard Library rather than as language features, the cmath
header file must be included in order to use them, with the functions scoped by the std::
prefix:
#include <cmath> // Put this at top of the file. double trig_fcn(double theta, double phi) { return std::sin(theta) + std::cos(phi); } // Or, alternatively double zero_coupon_bond(double face_value, double int_rate, double year_fraction) { using std::exp; return face_value * exp(-int_rate * year_fraction); }
Two points to note regarding <cmath>
functions are as follows. First, C++ has no power operator. Unlike other languages, which typically indicate an exponent by a ^
or a **
operator, this does not exist as a C++ language feature (the operator ^
does exist but is not used for this purpose). Instead, we need to call the Standard Library std::pow(.)
function in <cmath>
. When computing polynomials, however, it may be more efficient to apply factoring per Horner’s method and reduce the number of multiplicative operations, as described in Section 8.1 of From Mathematics to Generic Programming by Alexander Stepanov and Daniel Rose (Addison-Wesley, 2014). For example, if we wish to implement the function
it can be written in C++ as
double f(double x) { return x * (x * (x * (8.0 * x + 7.0) + 4.0 * x) - 10.0) - 6.0; }
rather than
double f(double x) { return 8.0 * std::pow(x, 4) + 7.0 * std::pow(x, 3) + 4.0 * std::pow(x, 2) + 10.0 * x - 6.0; }
As implementation of std::pow
is vendor-specific, performance results may vary. There may also be differences due to compiler optimizations. However, Horner’s method does reduce the number of operations, and as such it is often preferred.
For a noninteger exponent, say,
there is no available alternative to using std::pow(.)
:
double g(double x, double y) { return std::pow(x, -1.368 * x) + 5.311 * y; }
Second, you might be able to use some of these functions without #include <cmath>
. This is unfortunately one of the quirks in C++ due to its long association with C; however, the moral of the story is quite simple: to keep C++ code ISO-compliant, and thus help ensure compatibility across different compilers and operating system platforms, we should always use #include <cmath>
and scope the math functions with std::
.
A prime example is the absolute value function. In some C++ Standard Library implementations, the default (global namespace) abs(.)
function might be a carryover from math.h
that was implemented only for integer types in C. To calculate the absolute value of a floating-point number in math.h
, we would need to use the fabs(.)
function. However, std::abs(.)
is overloaded for both integer and floating-point (e.g., double
) arguments and should be preferred.
It is also the case that the <cmath>
functions have been evolving separately from their C counterparts, including optimizations particular to C++. This is one more reason to prefer the Standard Library versions. The GNU C++ Library Manual explains:6
The standard specifies that if one includes the C-style header (
<math.h>
in this case), the symbols will be available in the global namespace and perhaps in namespacestd::
(but this is no longer a firm requirement). On the other hand, including the C++-style header (<cmath>
) guarantees that the entities will be found in namespacestd
and perhaps in the global namespace.
So, long story short: use #include <cmath>
and scope math functions with std::
.
Mathematical Special Functions
Mathematical special functions include Legendre polynomials, Hermite polynomials, Bessel functions, and the exponential integral. These were added to the Standard Library in C++17, and they also require inclusion of the <cmath>
header. These functions are often employed in physics, but as quantitative finance has a strong historical ties to the physics world, you may come across them in advanced derivatives modeling. In one of the most well-known and cited papers on options pricing, “Valuing American Options by Simulation: A Simple Least-Squares Approach” by Francis A. Longstaff and Eduardo S. Schwartz, both Legendre and Hermite polynomials can be used as a basis (among others) for the underlying model.
Further discussion is beyond the intended scope of this book, but you can find more details about the special math functions on cppreference.com.
Standard Library Mathematical Constants
A handy addition to the C++20 Standard Library is a set of commonly used mathematical constants, such as the values of , , and . Some of those that may be convenient for quantitative finance applications are shown in Table 1-2.
C++ constant | Definition |
---|---|
|
|
|
|
|
|
|
|
|
To use these constants, we must first include the <numbers>
header in the Standard Library. Each must be scoped with the std::numbers
namespace. For example, to implement the function
we could write this:
#include <cmath> #include <numbers> . . . double math_constant_fcn(double x, double y) { using namespace std::numbers; double math_inv_sqrt_two_pi = inv_sqrtpi / sqrt2; return math_inv_sqrt_two_pi * (std::sin(pi * x) + std::cos(inv_pi * y)); }
This way, whenever is used in calculations, for example, its value will be consistent throughout the program, rather than leaving it up to different programmers on a project who might use approximations out to varying precisions, resulting in possible inconsistencies in numerical results.
In addition, the value of , which can crop up somewhat frequently in mathematical calculations, does not have to be computed with
std::sqrt(2.0)
each time it is needed. The constant
std::numbers::sqrt2
holds the double-precision approximation itself. While perhaps of trivial consequence in terms of one-off performance, repeated calls to the std::sqrt
function millions of times in computationally intensive code would potentially have some effect.
Note
While not essential to know at this point, it is worth mentioning that these constants are fixed at compile time rather than computed with each call runtime, using a C++11 designation called constexpr
.
As a closing note, it is somewhat curious that the set of mathematical constants provided in C++20 include the value , but not or , despite the latter two being far more commonly present mathematical and statistical functions, including those used in finance. These latter two are, however, included in the Boost Math Toolkit library, covered in Chapter 9.
Naming Conventions
Guidelines on code formatting and variable naming are quite important when writing critical production code in financial systems, in a feature-rich language such as C++. Bugs, runtime errors, and program crashes are much more easily avoided or addressed if the source code is written in a consistent, clean, and maintainable state.
So far, we have just been plowing ahead with examples without discussing this topic. The examples have been fairly simple so far, but as code gets more involved, it is a good idea to step back and conclude this chapter with a brief discussion about naming conventions and coding style, and the style guidelines that are used in this book. More importantly, in real-life financial C++ development work, the code you work with and write will take a quantum leap in complexity, so these issues become a necessity.
To review some fundamentals from introductory C++, variable, function, and class names can be any contiguous combination of letters and numbers, subject to the following conditions:
-
Names must begin with a letter or an underscore; leading numerals are not allowed (nonleading numerals are). Leading underscores, however, are best avoided (see the Note that follows).
-
Other than the underscore character, special characters such as
@
,=
, and$
are not allowed.
-
Spaces are not allowed. Names must be contiguous.
-
Language keywords are reserved and are not allowed to also be names, such as
double
,if
, andwhile
.
You can find a complete listing of reserved keywords on cppreference.com.
Note
Technically speaking, names beginning with an underscore are legal; however, in reality, they are often reserved for implementations of the C++ Standard and so are typically discouraged per Section 5.10 of the C++ Standard, Working Draft. Some variable naming styles (for example, as exhibited in the Google C++ Style Guide) do prescribe trailing underscores for private class members—for example, member_variable_
. This meaning of a trailing underscore will also be adopted for code examples used in this book.
Single-letter variable and function names are fine for simple examples and plain mathematical functions. However, for quantitative financial models implementation, and for trading and risk systems, it will usually be better to pass function arguments with more descriptive names. Function and class names should also provide some indication of what they do. Furthermore, it is important to decide as a group or company on a set of naming and style rules in order to enhance code maintainability and reduce the risk of bugs getting into the code.
Several naming styles have been common over the years:
- Lower camel case
-
The first word is lowercase, and the first letter of the following words is capitalized:
optionDelta
,riskFreeRate
,efficientFrontier
- Upper camel case, aka Pascal case
-
The first letter of each word is uppercase:
OptionDelta
,RiskFreeRate
,EfficientFrontier
- Snake case
-
Each word is lowercase, separated by an underscore character:
option_delta
,risk_free_rate
,efficient_frontier
Lower camel and snake cases are typically found in C++ function and variable names, and class names are usually in upper camel form. Users should adopt the style of their team and in general be comfortable with each style. In this book, we will use snake case for function and variable names, and upper camel for class names. As noted earlier, trailing underscores will be used for private member variables and member functions; here’s an example:
class Blah { public: Blah(double x, . . .); void calc_blah(double y) const; . . . private: double x_; double do_something_(); };
When single characters are used for integral counting variables, using the Fortran convention of letters i
through n
is still common, although this is not required. We will also adopt this practice for the most part.
For an example of how not to write code, “How to Write Unmaintainable Code: Ensure a Job for Life ;-)” by Roedy Green provides a humorous but not irrelevant viewpoint. To quote Homer Simpson, “It’s funny because it’s true” (d’oh).
Summary
C++ is broadly divided into two components: language features and the Standard Library. Taken together, they are typically referred to as the Standard, and the major compiler vendors (particularly “the big three,” Microsoft Visual Studio, LLVM Clang, and GNU gcc) also include their respective Standard Library distributions with their compiler releases. Implementation of the Standard Library is mostly left up to the individual vendor, as long as they comply with the ISO Standard requirements.
There is some history behind the rise and fall in popularity of C++ in financial software development, and its subsequent resurgence. After some struggles competing with Java and C#, C++ started to experience a bit of a renaissance in this domain with the release of C++11. With subsequent releases every three years—currently C++23—new features and inexpensive abstractions have supplanted a lot of coding that once had to be done from scratch, resulting in cleaner code when used effectively. Some of these, such as the auto
keyword, range-based for
loops, scoped enums, lambda expressions, and mathematical constants, were discussed here. Further specific examples as applied to financial software follow in subsequent chapters.
Further Resources
A recommended resource for learning more about good modern C++ programming style guidelines is Chapter 3 of the freely available online book C++ Best Practices by Jason Turner. Its content is also relevant to topics covered in Chapters 2 and 3 of this book.
If you’re interested in Python/C++ integration with pybind11, Steven Zhang has created some straightforward examples on GitHub to help you get started.
For more information on getting started with Rcpp and integrating reusable C++ code in R packages, a series of blog posts by the author were published in 2020. The link provided points to the final post in the series, from which links to each post can be found, starting from the beginning.
1 Nicolai M. Josuttis, The C++ Standard Library: A Tutorial and Reference (Addison-Wesley, 2012), Chapter 1.
2 Christopher Di Bella, “Why do some C++ programs use size_t
?”, Quora.
3 “Prefer using
over typedef
for defining aliases”, ISO C++ Core Guidelines.
4 “Prefer class enums over plain enums”, ISO C++ Core Guidelines.
5 Item 31 of Effective Modern C++ by Scott Meyers, (O’Reilly, 2014), 217, 220.
6 Paolo Carlini et al., The GNU C++ Library Manual, “The C Headers and namespace std
.”
Get Learning Modern C++ for Finance now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.