1

尊敬的 Stack Exchange 专家,

我正在尝试建立一个类(多变量分布函数),它将升压分布存储在 std::vector (边际分布函数)中。虽然这可以使用 boost::variant (请参阅我的问题:Boost: Store Pointers to Distributions in Vector),但我也尝试了 boost::any。原因是使用变体时,我必须在设置变体时对潜在类型(边际分布)进行硬编码,而我想避免这种情况。

虽然不同实现的分布类不共享一个共同的父类,但有一些函数,如 boost::math::cdf 或 boost::math::pdf 可以应用于所有分布,并且我想应用迭代标准::向量。

使用我生成的任何以下代码(运行良好),但现在我遇到了函数 any_cdf 需要检查类型的问题。

虽然我在设置向量时规避了对类型的硬编码(至于变体),但我现在需要对 any_cdf 函数中的类型进行硬编码(而具有变体的解决方案可以通过模板化的访问者函数处理 cdf 函数的应用,因此没有任何类型规范),这意味着要管理大量代码,大量 if 语句......

但是,逻辑根本没有改变(我转换了类型,然后在所有 if 语句中应用了 cdf 函数),如果除了 boost 分布之外的其他内容存储在列表中,我也不会真正关心函数的行为。

那么有没有机会得到我的蛋糕并吃掉它,这意味着不会被迫在 any_cdf 中硬编码发行版的铸造类型(很像变体的模板化访问者函数)?

非常感谢您的帮助,H.

Ps 如果这不可行,在这种情况下,我通常会更好地使用 boost::any 或 boost::variant 吗?

#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;
}

编辑:关于评论,任何似乎都不是手头任务的最简单/最佳选择。然而,出于完整性原因,类似 boost::any 的实现的访问者在以下位置讨论: boost::any 的访问者模式

4

3 回答 3

3

注意请参阅我的旧答案以讨论解决方案 a vector 和boost::anyvs. boost::variant

如果您实际上并不需要动态的分布向量 - 而只是想应用一个静态已知的分布列表,您可以“摆脱”其中的一个tuple<>

现在,通过 Phoenix 和 Fusion 的一点(嗯,很多)魔法,您可以“只是”将该cdf功能调整为Lazy Actor

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

在这种情况下,等效的 扩展代码示例会缩小为: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;
}

哇。那是……几乎没有 6 行代码 :) 最好的部分是它已经与 c++03 兼容了。

于 2014-03-26T17:18:03.253 回答
2

更新这是假设向量和boost::anyvs.的答案boost::variant。如果您可以使用tuple<> 请参阅我的其他答案

您最终将以一种或另一种方式对潜在类型进行硬编码。

使用变体,您可以使用访问者对复杂性进行分组和隐藏:

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...);
     }
};

这可以应用于您的变体,例如

boost::apply_visitor(invoke_member_foo(), my_variant);

使用 boost any,您可以以枯燥和手动的方式进行类型切换:

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 这显然不利于可维护性,例如

  • 您无法使用足够相似的元素类型轻松扩展类型列表以满足相同的概念并支持它 - 您需要手动将案例添加到类型开关(如果您不这样做 - 您运气不好,没有错误,你会有(无声的)错误。只要variant你的访问者不处理你的类型,你就会得到一个编译错误

  • 这项工作 ^(类型切换)对于您要全面实施的每个操作都会重复。当然,您可以实现一次类型切换,并将实际实现作为仿函数提供,但此时您将实现与我为变体展示的 a完全相同static_visitor的实现,除了效率低得多的实现。

  • boost::any只能包含CopyConstructible. Boostvariant甚至可以包含引用(例如boost::variant<dist1_t&, dist2_t&>)并具有(一些)移动语义支持

简而言之,boost::any节省了提前考虑的时间,但它所做的只是将工作转移到呼叫站点。


积极的一面,让我与您分享一个我喜欢的成语,它使访问者可以像普通的免费功能一样访问。让我们any_cdf为变体重写你的函数:

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);
}

可以在Live On Coliru上找到完整的运行程序

演示列表

#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;
}

实际上,虽然我使用了一些 c++11,但使用一些 c++1y 特性(如果你的编译器有这些特性)可以使这更加漂亮。

最后,你也可以为 c++03 工作;它只需要比我目前花费更多的时间。

于 2014-03-26T16:20:22.783 回答
2

关于什么:

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;
}

但是,将参数绑定到函数模板可能很棘手。

于 2014-03-26T18:13:20.390 回答