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.hppstan/math/prim/arr.hppstan/math/prim/mat.hppstan/math/rev/scal.hppstan/math/rev/arr.hpp-
stan/math/rev/mat.hpp: this is whatstan/math.hppincludes stan/math/fwd/scal.hppstan/math/fwd/arr.hppstan/math/fwd/mat.hppstan/math/mix/scal.hppstan/math/mix/arr.hppstan/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.