Вопрос

Dear Stack Exchange Experts,

I am trying to set up a class (multivariate distribution function) that stores boost distributions in a std::vector (marginal distribution functions). While this is possible using boost::variant (see my question: Boost: Store Pointers to Distributions in Vector), I also gave boost::any a try. The reason being that with variant I have to hard-code the potential types (marginal distributions) when setting up the variant and I wanted to avoid this.

While the different implemented distribution classes do not share a common parent class, there are functions such as boost::math::cdf or boost::math::pdf that can be applied to all distributions, and that I want to apply iterating over the std::vector.

Working with any I produced the code below (which is running fine), but now I have the problem that the function any_cdf needs to check the types.

While I circumvented hard-coding the types when setting up the vector (as for variant) I now need to hard-code the types in the any_cdf function (while the solution with variants can handle the application of the cdf function via a templated visitor function, and thus without any type specifications) which means lots of code to manage, lots of if statements...

However, the logic does not change at all (I cast the type, then apply the cdf function in all if statements), and I wouldn't really care how the function behaves if something other than a boost distribution gets stored in the list.

So is there any chance to have my cake and eat it, meaning not being forced to hard-code the casting type of the distribution in any_cdf (much like a templated visitor function for variants)?

Thanks so much for your help, H.

P.s. if this is not feasible, would I generally be better of with boost::any or boost::variant in this situation?

#include <boost/math/distributions.hpp>
#include <boost/any.hpp>
#include <vector>
#include <iostream>
#include <limits>

//template function to apply cdf
template<class T> T any_cdf(boost::any a, T &x){

    //declare return value
    T y;

    //cast any with hardcoded types
    if (a.type() == typeid(boost::math::normal_distribution<T>)){

    y = boost::math::cdf(boost::any_cast< boost::math::normal_distribution<T> >(a),x);

    } else if (a.type() == typeid(boost::math::students_t_distribution<T>)){

    y = boost::math::cdf(boost::any_cast< boost::math::students_t_distribution<T> >(a), x);

    } else {
    //return NaN in case of failure or do something else (throw exception...)
    y =  std::numeric_limits<T>::quiet_NaN();
    }

    return(y);
}



int main (int, char*[])
{
    //get distribution objects
    boost::math::normal_distribution<double> s;
    boost::math::students_t_distribution<double> t(1);

    //use any to put just any kind of objects in one vector 
    std::vector<boost::any> vec_any;
    vec_any.push_back(s);
    vec_any.push_back(t);

    //evaluation point and return value 
    double y;
    double x = 1.96;

    for (std::vector<boost::any>::const_iterator iter = vec_any.begin(); iter != vec_any.end(); ++iter){

        y = any_cdf<double>(*iter,x);
        std::cout << y << std::endl;

    }

    return 0;
}

Edit: Concerning the comments any seems not to be the easiest/best choice for the task at hand. However for completeness reasons a visitor like implementation for boost::any is discussed at: visitor pattern for boost::any

Это было полезно?

Решение 2

Update This is the answer assuming a vector and boost::any vs. boost::variant. If you can use a tuple<> 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 are CopyConstructible. Boost variant 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.

Другие советы

Note See my older answer for a discussion of solutions a vector and boost::any vs. boost::variant.

If you don't actually need a dynamic vector of distributions - but just want to apply a statically known list of distributions, you can "get away" with a tuple<> of them.

Now, with a bit (well, a lot) of magic from Phoenix and Fusion, you can "just" adapt the cdf function as a Lazy Actor:

BOOST_PHOENIX_ADAPT_FUNCTION(double, cdf_, boost::math::cdf, 2)

In which case an equivalent extended code sample shrinks to: See it Live On Coliru

int main()
{
    typedef boost::tuple<bm::normal, bm::students_t> Dists;
    Dists dists(bm::normal(), bm::students_t(1));

    double x = 1.96;

    boost::fusion::for_each(dists, std::cout << cdf_(arg1, x) << "\n");

    std::cout << "\nComposite (multiplication):\t" << boost::fusion::accumulate(dists, 1.0, arg1 * cdf_(arg2, x));
    std::cout << "\nComposite (mean):\t\t"         << boost::fusion::accumulate(dists, 0.0, arg1 + cdf_(arg2, x)) / boost::tuples::length<Dists>::value;
}

Whoah. That's... hardly 6 lines of code :) And the best part is it's all c++03 compatible already.

What about:

int main (int, char*[])
{
    boost::math::normal_distribution<double> s;
    boost::math::students_t_distribution<double> t(1);

    typedef std::vector<boost::function<double (double)> > vec_t; 
    vec_t vec_func;
    vec_func.push_back(boost::bind(boost::math::cdf<double>, boost::ref(s), _1));
    vec_func.push_back(boost::bind(boost::math::cdf<double>, boost::ref(t), _1));

    //evaluation point and return value 
    double y;
    double x = 1.96;

    for (vec_t::const_iterator iter = vec_func.begin(); iter != vec_func.end(); ++iter){
        y = (*iter)(x);
        std::cout << y << std::endl;
    }

    return 0;
}

Binding argument to a function template can be tricky though.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top