Boost.Proto : 如何制作原始数组的表达式终端而不是 std::vector?

Boost.Proto : How to make an expression terminal of a primitive array instead of std::vector?

现在我正在尝试为矢量表达式制作另一种迷你 EDSL(嵌入式领域特定语言)。实际上 Boost.Proto 用户指南已经提供了这样一个 EDSL 示例,“Lazy Vector”,其中向量表达式由 std::vector<T> 组成。但是我必须改用原始数组的那些表达式。因为原始数组运算仍然是几个科学模拟程序的核心。

因此,我向 "Lazy Vector" 代码添加了一个数组包装器 class、ArrayWrapper,并将 std::vector 替换为 ArrayWrapper。此修改后的源代码已成功编译和链接。但是当我 运行 它时,核心被转储了。

这里是源代码的修改版本:

//  The original version of this file is :
//  "Lazy Vector: Controlling Operator Overloads"
//  in Boost.Proto users' guide.
//  Copyright 2008 Eric Niebler. Distributed under the Boost
//  Software License, Version 1.0.
//
//  It was modified to try protofying a primitive array
//  on May 19 2015.

    #include <vector>
    #include <iostream>
    #include <boost/mpl/int.hpp>
    #include <boost/proto/core.hpp>
    #include <boost/proto/context.hpp>
    namespace mpl = boost::mpl;
    namespace proto = boost::proto;
    using proto::_;


    template <typename T>
    class ArrayWrapper {
    private:
        T* data;
        size_t size_;

    public:
        typedef T value_type;

        explicit ArrayWrapper(std::size_t size = 0, T const & value = T() ):
            data( new T[size]), size_(size) {
            for (std::size_t i = 0; i < size_; i++) data[i] = value;

        }       

        ~ArrayWrapper() {
            std::cerr << "Now destructing an ArrayWrapper" << std::endl;
            delete [] data;
        }

        std::size_t size() { return size_; }

        T& operator[](std::size_t i) { return data[i]; }
        T operator[](std::size_t i) const { return data[i]; }
    };


    template<typename Expr>
    struct lazy_vector_expr;

    // This grammar describes which lazy vector expressions
    // are allowed; namely, vector terminals and addition
    // and subtraction of lazy vector expressions.
    struct LazyVectorGrammar
      : proto::or_<
            proto::terminal< ArrayWrapper<_> >
          , proto::plus< LazyVectorGrammar, LazyVectorGrammar >
          , proto::minus< LazyVectorGrammar, LazyVectorGrammar >
        >
    {};

    // Tell proto that in the lazy_vector_domain, all
    // expressions should be wrapped in laxy_vector_expr<>
    // and must conform to the lazy vector grammar.
    struct lazy_vector_domain
      : proto::domain<proto::generator<lazy_vector_expr>, LazyVectorGrammar>
    {};

    // Here is an evaluation context that indexes into a lazy vector
    // expression, and combines the result.
    template<typename Size = std::size_t>
    struct lazy_subscript_context
    {
        lazy_subscript_context(Size subscript)
          : subscript_(subscript)
        {}

        // Use default_eval for all the operations ...
        template<typename Expr, typename Tag = typename Expr::proto_tag>
        struct eval
          : proto::default_eval<Expr, lazy_subscript_context>
        {};

        // ... except for terminals, which we index with our subscript
        template<typename Expr>
        struct eval<Expr, proto::tag::terminal>
        {
            typedef typename proto::result_of::value<Expr>::type::value_type result_type;

            result_type operator ()( Expr const & expr, lazy_subscript_context & ctx ) const
            {
                return proto::value( expr )[ ctx.subscript_ ];
            }
        };

        Size subscript_;
    };

    // Here is the domain-specific expression wrapper, which overrides
    // operator [] to evaluate the expression using the lazy_subscript_context.
    template<typename Expr>
    struct lazy_vector_expr
      : proto::extends<Expr, lazy_vector_expr<Expr>, lazy_vector_domain>
    {
        lazy_vector_expr( Expr const & expr = Expr() )
          : lazy_vector_expr::proto_extends( expr )
        {}

        // Use the lazy_subscript_context<> to implement subscripting
        // of a lazy vector expression tree.
        template< typename Size >
        typename proto::result_of::eval< Expr, lazy_subscript_context<Size> >::type
        operator []( Size subscript ) const
        {
            lazy_subscript_context<Size> ctx(subscript);
            return proto::eval(*this, ctx);
        }
    };

    // Here is our lazy_vector terminal, implemented in terms of lazy_vector_expr
    template< typename T >
    struct lazy_vector
      : lazy_vector_expr< typename proto::terminal< ArrayWrapper<T> >::type >
    {
        typedef typename proto::terminal< ArrayWrapper<T> >::type expr_type;

        lazy_vector( std::size_t size = 0, T const & value = T() )
          : lazy_vector_expr<expr_type>( expr_type::make( ArrayWrapper<T>(size, value) ) )
        {}

        // Here we define a += operator for lazy vector terminals that
        // takes a lazy vector expression and indexes it. expr[i] here
        // uses lazy_subscript_context<> under the covers.
        template< typename Expr >
        lazy_vector & operator += (Expr const & expr)
        {
            std::size_t size = proto::value(*this).size();
            for(std::size_t i = 0; i < size; ++i)
            {
                proto::value(*this)[i] += expr[i];
            }
            return *this;
        }
    };

    int main()
    {
        // lazy_vectors with 4 elements each.
        lazy_vector< double > v1( 4, 1.0 ), v2( 4, 2.0 ), v3( 4, 3.0 );

        // Add two vectors lazily and get the 2nd element.
        double d1 = ( v2 + v3 )[ 2 ];   // Look ma, no temporaries!
        std::cout << d1 << std::endl;

        // Subtract two vectors and add the result to a third vector.
        v1 += v2 - v3;                  // Still no temporaries!
        std::cout << '{' << v1[0] << ',' << v1[1]
                  << ',' << v1[2] << ',' << v1[3] << '}' << std::endl;

        // This expression is disallowed because it does not conform
        // to the LazyVectorGrammar
        //(v2 + v3) += v1;

        return 0;
    }

我想我的数组包装器 class 具有 "Lazy vector" 程序的其余部分所需的所有必要成员函数。我认为这些成员函数的接口与原始"Lazy Vector"程序使用的std::vector成员函数的接口相同。

可能我漏掉了一些要点。但是如何解决呢? (我应该如何使用原始数组制作 proto::terminal<T> 对象?)如果您能给我建议或提示,我将不胜感激。

我希望我可能已经部分解决了我的问题。在将复制构造函数添加到 ArrayWrapper class 后,Boost.Proto 用户指南中 "Lazy Vector" 示例的修改版本可以正常工作。

这是源代码:

    //  The original version of this file is :
    //  "Lazy Vector: Controlling Operator Overloads"
    //  in Boost.Proto users' guide.
    //  Copyright 2008 Eric Niebler. Distributed under the Boost
    //  Software License, Version 1.0.
    //
    //  It was modified to try protofying a primitive array
    //  on May 20 2015.

    #include <vector>
    #include <iostream>
    #include <boost/mpl/int.hpp>
    #include <boost/proto/core.hpp>
    #include <boost/proto/context.hpp>
    namespace mpl = boost::mpl;
    namespace proto = boost::proto;
    using proto::_;

    template <typename T>
    class ArrayWrapper {
    private:
        T* data;
        size_t size_;

    public:
        typedef T value_type;

        explicit ArrayWrapper(std::size_t size = 0, T const & value = T() ):
            data( new T[size]), size_(size) {
            for (std::size_t i = 0; i < size_; i++) data[i] = value;

        }
        ArrayWrapper(const ArrayWrapper<T>& wrapper):
            data( new T[ wrapper.size_] ), size_(wrapper.size_) {
            for (std::size_t i = 0; i < size_; i++) data[i] = wrapper.data[i];
        }


        ~ArrayWrapper() {
            std::cerr << "Now destructing an ArrayWrapper" << std::endl;
            delete [] data;
        }

        std::size_t size() {
            return size_;
        }

        T& operator[](std::size_t i) { return data[i]; }
        T operator[](std::size_t i) const { return data[i]; }
    };



    template<typename Expr>
    struct lazy_vector_expr;

    // This grammar describes which lazy vector expressions
    // are allowed; namely, vector terminals and addition
    // and subtraction of lazy vector expressions.
    struct LazyVectorGrammar
      : proto::or_<
            proto::terminal< ArrayWrapper<_> >
          , proto::plus< LazyVectorGrammar, LazyVectorGrammar >
          , proto::minus< LazyVectorGrammar, LazyVectorGrammar >
        >
    {};

    // Tell proto that in the lazy_vector_domain, all
    // expressions should be wrapped in laxy_vector_expr<>
    // and must conform to the lazy vector grammar.
    struct lazy_vector_domain
      : proto::domain<proto::generator<lazy_vector_expr>, LazyVectorGrammar>
    {};

    // Here is an evaluation context that indexes into a lazy vector
    // expression, and combines the result.
    template<typename Size = std::size_t>
    struct lazy_subscript_context
    {
        lazy_subscript_context(Size subscript)
          : subscript_(subscript)
        {}

        // Use default_eval for all the operations ...
        template<typename Expr, typename Tag = typename Expr::proto_tag>
        struct eval
          : proto::default_eval<Expr, lazy_subscript_context>
        {};

        // ... except for terminals, which we index with our subscript
        template<typename Expr>
        struct eval<Expr, proto::tag::terminal>
        {
            typedef typename proto::result_of::value<Expr>::type::value_type result_type;

            result_type operator ()( Expr const & expr, lazy_subscript_context & ctx ) const
            {
                return proto::value( expr )[ ctx.subscript_ ];
            }
        };

        Size subscript_;
    };

    // Here is the domain-specific expression wrapper, which overrides
    // operator [] to evaluate the expression using the lazy_subscript_context.
    template<typename Expr>
    struct lazy_vector_expr
      : proto::extends<Expr, lazy_vector_expr<Expr>, lazy_vector_domain>
    {
        lazy_vector_expr( Expr const & expr = Expr() )
          : lazy_vector_expr::proto_extends( expr )
        {}

        // Use the lazy_subscript_context<> to implement subscripting
        // of a lazy vector expression tree.
        template< typename Size >
        typename proto::result_of::eval< Expr, lazy_subscript_context<Size> >::type
        operator []( Size subscript ) const
        {
            lazy_subscript_context<Size> ctx(subscript);
            return proto::eval(*this, ctx);
        }
    };

    // Here is our lazy_vector terminal, implemented in terms of lazy_vector_expr
    template< typename T >
    struct lazy_vector
      : lazy_vector_expr< typename proto::terminal< ArrayWrapper<T> >::type >
    {
        typedef typename proto::terminal< ArrayWrapper<T> >::type expr_type;

        lazy_vector( std::size_t size = 0, T const & value = T() )
          : lazy_vector_expr<expr_type>( expr_type::make( ArrayWrapper<T>(size, value) ) )
        {}

        // Here we define a += operator for lazy vector terminals that
        // takes a lazy vector expression and indexes it. expr[i] here
        // uses lazy_subscript_context<> under the covers.
        template< typename Expr >
        lazy_vector & operator += (Expr const & expr)
        {
            std::size_t size = proto::value(*this).size();
            for(std::size_t i = 0; i < size; ++i)
            {
                proto::value(*this)[i] += expr[i];
            }
            return *this;
        }
    };

    int main()
    {
        // lazy_vectors with 4 elements each.
        lazy_vector< double > v1( 4, 1.0 ), v2( 4, 2.0 ), v3( 4, 3.0 );

        // Add two vectors lazily and get the 2nd element.
        double d1 = ( v2 + v3 )[ 2 ];   // Look ma, no temporaries!
        std::cout << d1 << std::endl;

        // Subtract two vectors and add the result to a third vector.
        v1 += v2 - v3;                  // Still no temporaries!
        std::cout << '{' << v1[0] << ',' << v1[1]
                  << ',' << v1[2] << ',' << v1[3] << '}' << std::endl;

        // This expression is disallowed because it does not conform
        // to the LazyVectorGrammar
        //(v2 + v3) += v1;

        return 0;
    }

但我不确定为什么 ArrayWrapper 的默认复制构造函数在我显式定义复制构造函数之前会导致核心转储。也许拷贝构造函数被proto::expr< proto::tag::terminal, proto::term< ArrayWrapper<T>>>class调用,当expr_type::make( ArrayWrapper<T>(size, value) ) )inlazy_vectorclass构造函数初始化expr_type[=34=的一个数据成员](参见 the synopsis of proto::expr)。正如您在 lazy_vector class 的定义中所见,expr_typetypedef 定义为 proto::expr< proto::tag::terminal, proto::term< ArrayWrapper<T>>>,因此其数据成员的类型 proto_childN,变成 ArrayWrapper<T>

此外,剩下的问题是 ArrayWrapper<T> 对象的那些复制操作减慢了程序的速度,这与表达式模板的目的相反。所以我应该承认我的回答不够好​​。我正在努力寻找更好的答案...

最后我找到了一种方法来制作原始数组的表达式终端,从而为矢量代数制作最小的 EDSL。它可以在初始化表达式模板的终端对象时抑制临时对象的多余副本。消除对象复制的关键是在 Vector class, ' 中放入一个原始数组,定义一个 returns true 的 trait for this Vector class,并使用 BOOST_PROTO_DEFINE_OPERATORS().

这是源代码:

#include <iostream>
#include <boost/proto/proto.hpp>

namespace mpl = boost::mpl;
namespace proto = boost::proto;

// This grammar describes which vector expressions
// are allowed; namely, vector terminals and addition
// and subtraction of vector expressions.
struct VecGrammar : proto::or_<
    proto::terminal< proto::_ >,
    proto::plus< VecGrammar, VecGrammar>,
    proto::minus< VecGrammar, VecGrammar>
> {};


// The above grammar is associated with this domain.
template<typename Expr> struct VecExpr;
struct VecDomain
    : proto::domain<proto::generator<VecExpr>, VecGrammar> {};


//
// Context for evaluating an element of matrix expressions
//
struct SubscriptCntxt
    : proto::callable_context<const SubscriptCntxt> {
        typedef double result_type;

        int index;
        SubscriptCntxt(int index_) :  index(index_) {}

        // matrix element
        template<typename Vector>
        double operator()(proto::tag::terminal, const Vector& vec) const {
            return vec[index];
        }

        // addition of vector expression terms
        template<typename E1, typename E2>
        double operator()(proto::tag::plus, const E1& e1, const E2& e2) const {
            return proto::eval(e1, *this) + proto::eval(e2, *this);
        }

        // substraction of vector expression terms
        template<typename E1, typename E2>
        double operator()(proto::tag::minus, const E1& e1, const E2& e2) const {
            return proto::eval(e1, *this) - proto::eval(e2, *this);
        }
};


//
// Vector Expression Templates
//
template<typename Expr>
struct VecExpr
    : proto::extends<Expr, VecExpr<Expr>, VecDomain> {
        explicit VecExpr(const Expr& e)
            : proto::extends<Expr, VecExpr<Expr>, VecDomain>(e) {
        }

        // Use a SubscriptCntxt instance to implement subscripting
        // of a vector expression tree.
        typename proto::result_of::eval< Expr, SubscriptCntxt>::type
        operator [](int i) const {
            const SubscriptCntxt ctx(i);
            return proto::eval(*this, ctx);
        }
};

//
// Matrix data are stored in an heap array.
//
class Vector {
    private:
        int sz;
        double* data;

public:
    explicit Vector(int sz_ = 1, double iniVal = 0.0) :
        sz( sz_), data( new double[sz] ) {
        for (int i = 0; i < sz; i++) data[i] = iniVal;
        std::cout << "Created" << std::endl;
    }
    Vector(const Vector& vec) :
        sz( vec.sz), data( new double[sz] ) {
        for (int i = 0; i < sz; i++) data[i] = vec.data[i];
        std::cout << "Copied" << std::endl;
    }

    ~Vector() {
        delete [] data;
        std::cout << "Deleted" << std::endl;
    }

    // accesing to a vector element
    double& operator[](int i) { return data[i]; }
    const double& operator[](int i) const { return data[i]; }

    // assigning the lhs of a vector expression into this matrix
    template<typename Expr>
    Vector& operator=( const Expr& expr ) {
        for(int i=0; i < sz; ++i) {
                // evaluating the i'th element of a matrix expression
                const SubscriptCntxt ctx(i);
                data[i] = proto::eval(proto::as_expr<VecDomain>(expr), ctx);
        }
        return *this;
    }

    // assigning and adding the lhs of a vector expression into this matrix
    template<typename Expr>
    Vector& operator+=( const Expr& expr ) {
        for(int i=0; i < sz; ++i) {
                // evaluating the (i,j) element of a matrix expression
                const SubscriptCntxt ctx(i);
                data[i] += proto::eval(proto::as_expr<VecDomain>(expr), ctx);
        }
        return *this;
    }
};


// Define a trait for detecting vector terminals, to be used
// by the BOOST_PROTO_DEFINE_OPERATORS macro below.
template<typename> struct IsVector : mpl::false_ {};
template<> struct IsVector<Vector> : mpl::true_  {};



namespace VectorOps {
    // This defines all the overloads to make expressions involving
    // Vector objects to build expression templates.
    BOOST_PROTO_DEFINE_OPERATORS(IsVector, VecDomain)
}

int main()
{
    using namespace VectorOps;

    // lazy_vectors with 4 elements each.
    Vector v1( 4, 1.0 ), v2( 4, 2.0 ), v3( 4, 3.0 );

    // Add two vectors lazily and get the 2nd element.
    double d1 = ( v2 + v3 )[ 2 ];   // Look ma, no temporaries!
    std::cout << d1 << std::endl;

    // Subtract two vectors and add the result to a third vector.
    v1 += v2 - v3;                  // Still no temporaries!
    std::cout << '{' << v1[0] << ',' << v1[1]
              << ',' << v1[2] << ',' << v1[3] << '}' << std::endl;

    // This expression is disallowed because it does not conform
    // to the LazyVectorGrammar
    //(v2 + v3) += v1;

    return 0;
}

我确认此代码有效并且输出与 Boost.Proto 用户指南中的 "Lazy Vector" 示例几乎相同。

尽管我仍然不确定 Boost.Proto 的幕后情况如何,但用它制作 EDSL 原型非常有趣。