Update This is the answer assuming a vector and
boost::any
vs.boost::variant
. If you can use atuple<>
see my other answer
You will end up hardcoding the potential types one way or another.
With variant, you can group and hide the complexities by using visitor:
struct invoke_member_foo : boost::static_visitor<double>
{
template <typename Obj, typename... Args>
double operator()(Obj o, Args const&... a) const {
return o.foo(a...);
}
};
This can be applied to your variant like
boost::apply_visitor(invoke_member_foo(), my_variant);
With boost any, you'd do the typeswitching the boring and manual way:
if (auto dist1 = boost::any_cast<distribution1_t>(&my_any))
dist1->foo();
else if (auto dist2 = boost::any_cast<distribution2_t>(&my_any))
dist2->foo();
else if (auto dist3 = boost::any_cast<distribution3_t>(&my_any))
dist3->foo();
IMO this is clearly inferior for maintainability e.g.
you can't easily extend the type list with an element type that is similar enough to satisfy the same concept and have it support - you'll need to add cases to the type-switch manually (and if you don't - you're out of luck, there is no error and you'll have (silent) bugs. With
variant
you'll just get a compile error whenever your visitor doesn't handle your type.this work ^ (the type switching) gets duplicated for each operation that you want to implement across the board. Of course, you can implement the type-switch once, and provide the actual implementation as a functor, but at that moment you'll have implemented the exact equivalent of a
static_visitor
as I showed for the variant, except with far less efficient implementation.boost::any
can only contain values that areCopyConstructible
. Boostvariant
can even contain references (e.g.boost::variant<dist1_t&, dist2_t&>
) and has (some) move-semantics support
In short, boost::any
saves on time thought in advance, but all it does is shift the work to the call-sites.
On a positive note, let me share with you an idiom I like, which makes visitors accessible as ordinary free functions. Let's rewrite your any_cdf
function for the variant:
namespace detail
{
template <typename T> struct var_cdf_visitor : boost::static_visitor<T> {
template <typename Dist>
T operator()(Dist& dist, T& x) const { return boost::math::cdf(dist, x); }
};
}
template<class T> T var_cdf(VarDist<T> a, T &x)
{
static detail::var_cdf_visitor<T> vis;
return boost::apply_visitor(
boost::bind(vis, ::_1, boost::ref(x)),
a);
}
A full running program can be found Live On Coliru
Demo Listing
#include <boost/bind.hpp>
#include <boost/math/distributions.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <limits>
#include <vector>
namespace detail
{
template <typename T> struct var_cdf_visitor : boost::static_visitor<T> {
template <typename Dist>
T operator()(Dist const& dist, T const& x) const { return boost::math::cdf(dist, x); }
};
}
template<class T, typename... Dist> T var_cdf(boost::variant<Dist...> const& a, T const& x) {
return boost::apply_visitor(boost::bind(detail::var_cdf_visitor<T>(), ::_1, x), a);
}
int main()
{
namespace bm = boost::math;
typedef std::vector<boost::variant<bm::normal, bm::students_t> > Vec;
Vec vec { bm::normal(), bm::students_t(1) };
//evaluation point and return value
double x = 1.96;
for (auto& dist : vec)
std::cout << var_cdf(dist,x) << std::endl;
}
Actually, though I used a bit of c++11, this could be made even prettier using some c++1y features (if your compiler has them).
And lastly, you can make work for c++03 too; it would just require more time than I currently have to throw at it.