Types and casting

Couple questions about types and casting:

I have a function for the Math library that accepts two arguments. I want those arguments to be limited to:

int, double,
double, double
double, var
var, var
double, fvar<var>
fvar<var>, fvar<var>
double, fvar<fvar<var>>
fvar<fvar<var>>, fvar<fvar<var>>

(all the autodiff types and mixes of types a Stan model might need to use)

What I ended up hacking together was:

typename boost::disable_if_c<is_vector_like<T1>::value || is_vector_like<T2>::value, myReturnType >::type

Is there any standard way of doing this? I saw is_var_or_arithmetic (https://github.com/stan-dev/math/blob/develop/stan/math/prim/scal/meta/is_var_or_arithmetic.hpp) but when I tried it (if I didn’t mess anything up) that would accept Eigen::Matrixes (Eigen::Matrices? Eigen::Matrixs?..)

What I want to avoid are Eigen::Matrixes and std::vectors leaking in as my T1s and T2s.

The other thing I wanted to know is if I have Eigen::Matrices of things I want to cast to other things (so like Eigen::Matrix<double> to Eigen::Matrix<var>).

I ended up using:

m.template cast<typename return_type<T1, T2>::type>()

Where m is being cast from one type to another. The issue I think with this is that it might allow casts in the wrong direction (vars to doubles instead of doubles to vars?). I’m honestly not 100% sure. Any preferred way to do this higher level cast? I assume internally Eigen’s cast is just using whatever copy constructors are available to it, but I’m not sure.

The only reason you ever need to limit arguments is to avoid instantiation ambiguity. What function are you trying to write?

Usually, if an argument is real in Stan, then we need to make sure we can instantiate it with any scalar type, including

  • primitives: int and double are scalar types
  • autodiff types: var is a scalar type, and fvar<T> is a scalar type if T is a scalar type

Given the way the autodiff (including from the Stan language) gets used, there is a guarantee that you never mix autodiff types in function calls. But you don’t need to enforce that.

So usually want we do is write binary functions with scalar returns with prim defining

template <typename T1, typename T2>
typename boost::math::tools::promote_args<T1, T2>::type
double foo(T1 x1, T2 x1);

which will handle arbitrary inputs if you take care with intermediate types. Then zero or more of the following more specific overloads can be defined for efficiency:

template <typename T>
var foo(const var& x1, T x2);

template <typename T>
var foo(T x1, const var& x2);

var foo(const var& x1, const var& x2);

and same for forward mode

template <typename T1, typename T2>
var foo(const fvar& x1, T2 x2);

template <typename T1, typename T2>
var foo(T1 x1, const fvar& x2);

template
var foo(const fvar& x1, const fvar& x2);


You have to be a bit careful if you don't define them all.  For instance, just defining the first two autodiff types in a set will lead to an ambiguity if its called with two autodiff variables.

I wouldn't trust our old metaprograms to do what it says on the tin.  Sean and I are going through and rewriting them to be more coherent in naming (and in behavior where possible).

Uh oh, I’m scared this has gone full circle (https://github.com/stan-dev/math/pull/554).

What was making things weird was when T1/T2 tried to be an Eigen matrix. I did some .cast stuff, and you responded:

These aren’t technically casts in C++. We run into this problem all over—last place I remember it coming up was for the conditional (ternary) operator; we also have something like this for assignment. Feel free to write to discourse or an issue asking how we’ve solved problems like this before.

I had a look at the append_col/row stuff and I think these things get handled by for loops and copy operators, but I figured if the suggestion was to ask, I’d try asking, haha. And now we’re talking on Discourse! Hi Bob!

It’s not that I forget the old topics, it’s just that there’s so much of this swirling around me I can’t keep the connections straight without a scorecard.

The tests all passed on whatever you’ve pushed up to origin in the pull request: https://github.com/stan-dev/math/pull/554

Is this meant to be done now? Or are you having problems getting things working?

Yup, it should be ready to go.

I can’t keep the connections straight without a scorecard.

Yeah haha, I was a little scared I was stripping too much context from the question.