The confusion of the include order of headers in the Math library came up again in a pull request for Automated Style.
I’ll put some thoughts down when I get a chance. For now, here’s some older discussion we’ve had regarding this topic:
- old stan-dev topic: C preprocessor for handling order-dependent includes
- Math issue #383: use preprocessor guards to error include order problems
What’s the current recommendation for clients of the Math library?
We are currently encouraging clients of the Math library that need reverse-mode automatic differentiation and matrix operations to include one header file: stan/math.hpp
That should cover most of the use cases, including code generated by Stan. There are 12 different top-level include files depending on what the client needs.
The first thing that needs to be determined is whether the client needs primitives (no auto-diff, prim
), reverse mode auto-diff (rev
), forward mode auto-diff (fwd
), or mixed mode auto-diff (for higher order, mix
).
The second thing that needs to be determined is if the client needs scalars only (no std::vector
or Eigen::Matrix
, prim
), arrays (std::vector
but not Eigen::Matrix
, arr
), or matrix (mat
). Once both things are determined, the client code just needs to include one of these headers:
stan/math/prim/scal.hpp
stan/math/prim/arr.hpp
stan/math/prim/mat.hpp
stan/math/rev/scal.hpp
stan/math/rev/arr.hpp
-
stan/math/rev/mat.hpp
: this is whatstan/math.hpp
includes stan/math/fwd/scal.hpp
stan/math/fwd/arr.hpp
stan/math/fwd/mat.hpp
stan/math/mix/scal.hpp
stan/math/mix/arr.hpp
stan/math/mix/mat.hpp
Why do we bother with 12 includes?
The reason we split prim
, rev
, fwd
, and mix
is because of compiler restrictions. In a perfect world, we’d just include mix
and we’d be able to compile everywhere. Unfortunately, most Windows users would not be able to compile anything if we included mix
. They should be able to include rev
and fwd
separately.
The main reason for splitting prim
, arr
, and mat
is because we’ve had multiple users ask us to reduce dependencies. By that, they really mean Boost and Eigen. In prim
and arr
, we use Boost, but not Eigen. When we include mat
, we’re now using Eigen. (I think it’d be safe for us to squish prim
and arr
together if we wanted.) This is something we didn’t do early on. We were able to do this a few years ago (with a lot of work). We could always reevaluate the tradeoffs in keeping this separate, but there’s a secondary reason. We may be able to cut down on the time to compilation if we keep this separate. If we don’t, then we’re always gonna be compiling the mat
version.
What’s the problem?
Our template metaprograms. And our desire to want to be efficient for compile time, runtime, and developer time.
Our code base is rad. For something like our distributions, we can write one function that be used for thousands of different instantiations (and that’s not an exaggeration). For example, we have the normal lpdf, which has three arguments, y
, mu
, sigma
. That header only includes the metaprograms from prim/scal/meta
.
When it’s in prim
and scal
, all three of them can only be double
(well… that’s not even true; they could be instantiated as float
or some other type, but not from the Stan library). If we change scal
to arr
, then each of the three arguments could independently be std::vector<double>
or double
. We’d need to change the template metaprograms included to go from the scal
to the arr
case. These need to be instantiated before including the normal lpdf file and once that’s done, this one function can now take on std::vector
arguments. And then again for arr
to mat
, but then we’re allowing for each of the arguments to be either double
, std::vector<double>
, Eigen::VectorXd
, or Eigen::RowVectorXd
.
But wait… there’s more! Change prim
to rev
and now we can replace double
with stan::math::var
. That requires a different set of the template metaprogram instantiations. And so on for fwd
and mix
.
So it’s really our template metaprograms. We can’t just include the right metaprograms for mix
and mat
, although that’s the easiest for the devs, because if we did that, we’d alienate a lot of users who wouldn’t be able to even compile the math library.
And include what you use is actually pretty tough. We’d have to expect the devs to know which includes are necessary for the 12 different folders. It’s not impossible, but it does require thought. We could have 12 different meta includes and I’m pretty sure that would fix it. It’d be pretty easy to replace the overall header with the meta header.