Today’s post is about the basic types we’re using in the library. Most of its content was prompted by comments made on a previous post; so thank you, Matt.
In last post, I mentioned that I’m looking into publishing Implementing QuantLib as an ebook, but I’m not sure if there’s any interest; please go read the post for details, if you haven’t already, and leave your feedback.
Odds and ends: basic types
The library interfaces don’t use built-in types; instead, a number of
typedefs are provided such as
Size. They are all mapped to basic types (we talked about using
full-featured types, possibly with range checking, but we dumped the
idea). Furthermore, all floating-point types are defined as
which in turn is defined as
double. This makes it possible to change
all of them consistently by just changing
In principle, this would allow one to choose the desired level of
accuracy; but to this, the test-suite answers “Fiddlesticks!” since it
shows a few failures when
Real is defined as
double. The value of the typedefs is really in making the code more
clear—and in allowing dimensional analysis for those who, like
me, were used to it in a previous life as a physicist; for instance,
expressions such as
r+s*t can be immediately flagged as
fishy if they are preceded by
Spread s, and
Of course, all those fancy types are only aliases to
double and the
compiler doesn’t really distinguish between them. It would nice if
they had stronger typing; so that, for instance, one could overload a
method based on whether it is passed a price or a volatility.
One possibility would be the
BOOST_STRONG_TYPEDEF macro, which is
one of the bazillion utilities provided by Boost. It is used as, say,
and creates a corresponding proper class with appropriate conversions
to and from the underlying type. This would allow overloading methods,
but has the drawbacks that not all conversions are explicit. This
would break backward compatibility and make things generally
awkward. For instance, a simple expression like
Time t = 2.0;
wouldn’t compile. You’d also have to write
f(Time(1.5)) instead of
f(1.5), even if
f wasn’t overloaded.
Also, the classes defined by the macro overload all operators: you can happily add a time to a rate, even though it doesn’t make sense (yes, dimensional analysis again). It would be nice if the type system prevented this from compiling, while still allowing, for instance, to add a spread to a rate yielding another rate or to multiply a rate by a time yielding a pure number.
How to do this in a generic way, and ideally with no run-time costs, was shown first by Barton and Nackman ; a variation of their idea is implemented in the Boost::Units library, and a simpler one was implemented once by yours truly while still working in Physics. (I won’t explain it here, but go look for it. It’s almost insanely cool.) However, that might be overkill here; we don’t have to deal with all possible combinations of length, mass, time and so on.
The ideal compromise for a future library might be to implement wrapper classes (à la Boost strong typedef) and to define explicitly which operators are allowed for which types. As usual, we’re not the first ones to have this problem: the idea has been floating around for a while, and a proposal was put forward  to add to the next version of C++ a new feature, called opaque typedefs, which would make it easier to define this kind of types.
A final note: among these types, there is at least one which is not
determined on its own (like
Time) but depends on other
types. The volatility of a price and the volatility of a rate have
different dimensions, and thus should have different types. In short,
Volatility should be a template type.
 J. Barton and L. R. Nackman, Dimensional Analysis, C++ Report,
 W. E. Brown, Toward Opaque Typedefs for C++1Y, v2, C++ Standard Committee Paper N3741, 2013