@cqfd was messaging me about some ideas to revamp a lot of our template metaprograms (these usually live in the various meta directories in the Math library) using the C++11 features (and some C++14 features) we have access to now, so I wanted to start a public thread on these ideas. Some people have been finding a few interesting use cases for variadic templates, though I think there are a lot that haven’t been touched yet. What are some other ideas? Here are a couple I’m aware of:
@cqfd was talking about replacing our template <> struct { value } patterns that you can see in many of our metaprograms with template variables, but those only become available in gcc 5 and we’re stuck on 4.9.3 until RTools migrates to a higher version next year. But I was thinking we might still be able to somehow use (possibly templated) constexpr functions to do something better now for at least the value structs.
@cqfd was also talking about switching to using for type aliases, which I think would be a good idea and might clarify the code a bit (and it’s recommended now).
Usage is roughly as pretty/gross as the current structs,
is_constant_struct(type<T_shape>{})
vs the current,
is_constant_struct<T_shape>::value
Oh well. The idea for using overloading instead of partial template specialiation (which functions in C++ famously don’t support) came from this blog post.
They’re admittedly not quite as flexible as the struct::value approach (they’re not structs, so you can’t inherit from them, and I don’t think you can use them as template arguments?), but they’re a little less noisy and I think they’re less annoying to write.
Another common thing to do (e.g. in the standard library) is to use the struct::value approach, but then give shortcuts like
I believe math/stan/math/prim/scal/meta/contains_nonconstant_struct.hpp and math/stan/math/prim/scal/meta/contains_vector.hpp both are possible candidates for modernization. Take contains_vector as an example. We could use the (not quite) recursion brought by variadic tempaltes to replace the following code.
This idea loses the double default types. Is this OK? My reading suggests that those are there as a default to false (not vector), which wouldn’t be necessary under the more modern pattern.
Yeah, definitely @roualdes, I was thinking along the same lines. (My gist above has a variable-template version of contains_nonconstant_struct.) I also wasn’t quite sure if it was ok to get rid of the double defaults, but seemed like it was.
You can keep the double default although in this case it doesn’t do anything (it just evaluates to false) by putting a default on the most general template (which is then inherited by its specialisations. It’s an error to have a default on a partial specialisation). See include_summand here https://github.com/stan-dev/math/pull/978
It’s quite possible that default didn’t make it into the final code, but Daniel and I talk about it at one point in the thread iirc (on my phone so can’t check. Sorry)
I posted some issues on things we want to do. One of the bigger ones is using auto where possible to preserve Eigen expression templates rather than evaluating them.
We want to use more of the algorithm libraries, standard copy, etc. A lot of things we can do will be simplified with bind-free lambdas. We should be using foreach loops. We should be using delegating constructors. Lots of stuff!
@roualdes, yeah, exactly. About your first question, there is a slightly more explicit way to write the recursion that makes the empty base case more obvious:
That ends up being equivalent to the code in the PR, just slightly shorter. The parent template in the PR says “I apply to any number of types”, and then specialization only succeeds if the types match those more specific templates (which only apply to non-empty parameter packs, and the most specific one to a parameter pack starting with std::vector).
I don’t really have a strong preference… I suppose the more explicit base case is easier to follow. I mention in the PR that if you need to do this kind of recursion a lot, you might want to have a higher-order template that checks if anything in a parameter pack matches a predicate; then you only have to get the recursion right once.
But about template modernization tricks, one thing I like about the style above is that you can express the recursion very succinctly with inheritance. (The standard library structs std::true_type and std::false_type just have a single boolean value field.) Walter Brown suggests doing something similar for type fields:
template <typename T> struct type_is { using type = T; };
// e.g. metafunction to remove cv qualifiers from a type
template <typename T> struct remove_cv : type_is<T> {};
template <typename T> struct remove_cv<T const> : type_is<T> {};
template <typename T> struct remove_cv<T volatile> : type_is<T> {};
Another maybe-not-huge simplification, but it’s cute :)