【问题标题】:Transforming an expression template tree转换表达式模板树
【发布时间】:2013-08-15 07:32:58
【问题描述】:

给定一个表达式模板树,我想在处理它之前创建一个新的优化树。考虑以下乘法运算示例:

a * b * c * d,

由于operator* 的从左到右的关联性,它产生了表达式树:

(((a * b) * c) * d).

我想生成一个转换后的表达式树,其中乘法从右到左发生:

(a * (b * (c * d))).

考虑二进制表达式类型:

template<typename Left, typename Right>
struct BinaryTimesExpr
{
    BinaryTimesExpr() = default;
    BinaryTimesExpr(const BinaryTimesExpr&) = default;
    BinaryTimesExpr(BinaryTimesExpr&&) = default;
    BinaryTimesExpr(Left&& l, Right&& r) : left(forward<Left>(l)), right(forward<Right>(r)) {}

    BinaryTimesExpr& operator=(const BinaryTimesExpr&) = default;
    BinaryTimesExpr& operator=(BinaryTimesExpr&&) = default;

    Left left;
    Right right;
};

定义乘法运算符operator*

template<typename Left, typename Right>
BinaryTimesExpr<Constify<Left>, Constify<Right>> operator*(Left&& l, Right&& r)
{
    return {forward<Left>(l), forward<Right>(r)};
}

Constify 定义为:

template<typename T> struct HelperConstifyRef     { using type = T;  };
template<typename T> struct HelperConstifyRef<T&> { using type = const T&; };
template<typename T>
using ConstifyRef = typename HelperConstifyRef<T>::type;

并用于确保从左值构造时具有 const 左值引用的子表达式,以及从右值构造时复制(通过复制/移动)右值。

使用前面的条件定义创建新表达式模板树的转换函数:

template<typename Expr>
auto Transform(const Expr& expr) -> Expr
{
    return expr;
}

template<typename Left, typename Right>
auto Transform(const BinaryTimesExpr<Left, Right>& expr) -> type(???)
{
    return {(Transform(expr.left), Transform(expr.right))};
}

template<typename Left, typename Right>
auto Transform(const BinaryTimesExpr<BinaryTimesExpr<LeftLeft, LeftRight>, Right>& expr) -> type(???)
{
    return Transform({Transform(expr.left.left), {Transform(expr.left.right), Transform(expr.right)}}); // this sintax is invalid...how can I write this?
}

我的问题是:

1) 如何确定Transform 函数的返回类型?我尝试过使用类型特征,例如:

template<typename Expr>
struct HelperTransformedExpr
{
    using type = Expr;
};

template<typename Left, typename Right>
struct HelperTransformedExpr<BinaryTimesExpr<Left, Right>>
{
    using type = BinaryTimesExpr<typename HelperTransformedExpr<Left>::type, typename HelperTransformedExpr<Right>::type>;
};

template<typename LeftLeft, typename LeftRight, typename Right>
struct HelperTransformedExpr<BinaryTimesExpr<BinaryTimesExpr<LeftLeft, LeftRight>, Right>>
{
    using type = BinaryTimesExpr<typename HelperTransformedExpr<LeftLeft>::type,
        BinaryTimesExpr<typename HelperTransformedExpr<LeftRight>::type, typename HelperTransformedExpr<Right>::type>>;
};

template<typename Expr>
using TransformedExpr = typename HelperTransformedExpr<Expr>::type;

但不知道如何应用它来解决我下面的问题 (2)。

2) 递归行怎么写:

return Transform({Transform(expr.left.left), {Transform(expr.left.right), Transform(expr.right)}});

3) 有没有针对这个问题的cleaner 解决方案?


编辑: DyP 为上述问题提供了部分解决方案。以下是我根据他的回答的完整解决方案:

template<typename Expr>
auto Transform(const Expr& expr) -> Expr
{
    return expr;
}

template<typename Left, typename Right>
auto Transform(BinaryTimesExpr<Left, Right> const& expr)
-> decltype(BinaryTimesExpr<decltype(Transform(expr.left)), decltype(Transform(expr.right))>{Transform(expr.left), Transform(expr.right)})
{
    return BinaryTimesExpr<decltype(Transform(expr.left)), decltype(Transform(expr.right))>{Transform(expr.left), Transform(expr.right)};
}

template<typename LeftLeft, typename LeftRight, typename Right>
auto Transform(BinaryTimesExpr<BinaryTimesExpr<LeftLeft, LeftRight>, Right> const& expr)
-> decltype(Transform(BinaryTimesExpr<decltype(Transform(expr.left.left)), BinaryTimesExpr<decltype(Transform(expr.left.right)), decltype(Transform(expr.right))>>{Transform(expr.left.left), {Transform(expr.left.right), Transform(expr.right)}}))
{
    return Transform(BinaryTimesExpr<decltype(Transform(expr.left.left)), BinaryTimesExpr<decltype(Transform(expr.left.right)), decltype(Transform(expr.right))>>{Transform(expr.left.left), {Transform(expr.left.right), Transform(expr.right)}});
}

int main()
{
    BinaryTimesExpr<int, int> beg{1,2};
    auto res = beg*3*4*5*beg;
    std::cout << res << std::endl;
    std::cout << Transform(res) << std::endl;
}

输出:

(((((1*2)*3)*4)*5)*(1*2))
(1*(2*(3*(4*(5*(1*2))))))

请注意,除了最外部的Transform 调用(参见最后的Transform 重载)之外,还必须对每个子表达式应用Transform 函数。

完整的源代码可以在here找到。

【问题讨论】:

  • 您是否考虑过使用 Boost.Proto 来帮助解决这个问题? Cpp Next 似乎目前处于关闭状态,但他写了一篇关于使用 Proto 重新排序数学表达式的文章。他的例子是 (A+B)[2] 到 A[2]+B[2],但是由于它们都只是运算符,因此应该适用。
  • 这将被集成到一个不需要第三方依赖的库中。
  • 1) Boost.Proto 仅是标头。 2)您是否想要将(((a * b) * c) * d) 转换为(a * (b * (c * d))) 的元编程转换器,或者直接将表达式树构建为(a * (b * (c * d))) 就足够了吗?
  • @Manu343726 我相信 DyP 是对的。我需要递归来彻底解决问题。此外,当我们进行矩阵乘法时,顺序很重要。
  • @Allan Order 对浮点类型也很重要。

标签: c++ c++11 metaprogramming expression-templates


【解决方案1】:

虽然 OP 想要一个不使用 Boost.Proto 的解决方案,但其他人可能有兴趣看看如何(很容易)使用它来完成:

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

namespace proto = boost::proto;
using proto::_;

struct empty {};

struct transform
  : proto::reverse_fold_tree<
        _
      , empty()
      , proto::if_<
            boost::is_same<proto::_state, empty>()
          , _
          , proto::_make_multiplies(_, proto::_state)
        >
    >
{};

int main()
{
    proto::literal<int> a(1), b(2), c(3), d(4);
    proto::display_expr( a * b * c * d );
    proto::display_expr( transform()(a * b * c * d) );
}

以上代码显示:

multiplies(
    multiplies(
        multiplies(
            terminal(1)
          , terminal(2)
        )
      , terminal(3)
    )
  , terminal(4)
)
multiplies(
    terminal(1)
  , multiplies(
        terminal(2)
      , multiplies(
            terminal(3)
          , terminal(4)
        )
    )
)

【讨论】:

    【解决方案2】:

    表达式实际上是一棵二叉树。例如,表达式a * b * c * d * e 被评估为((((a * b) * c) * d) * e),所以你有的是下面的树(对不起,像三岁孩子一样的绘图,ipad 没有手写笔......):

    如您所见,默认计算表达式是通过 left-side-first inorder 遍历树生成的。

    另一方面,反向评估表达式(OP 想要的)是通过 right-side-first-inorder 遍历树生成的:

    所以一种解决方法是在right-side-first-inorder中遍历生成的表达式树,并在这个过程中创建一个新的树。

    【讨论】:

    • 什么树更复杂(涉及更多的分支):(2*3)*(4*(5*6))?
    • @Allan 首先请注意,我的意思是 ((a*b)*c) 是表达式的形式(如何评估)a * b * c,这就是编译器如何根据运算符优先级规则。不是给定 ((a*b)*c) 等表达式生成的树。
    【解决方案3】:

    没有合并完美转发:

    #include <iostream>
    
    // simplified by making it an aggregate
    template<typename Left, typename Right>
    struct BinaryTimesExpr
    {
        Left left;
        Right right;
    };
    
    // "debug" / demo output
    template<typename Left, typename Right>
    std::ostream& operator<<(std::ostream& o, BinaryTimesExpr<Left, Right> const& p)
    {
        o << "(" << p.left << "*" << p.right << ")";
        return o;
    }
    
    // NOTE: removed reference as universal-ref yields a reference type for lvalues!
    template<typename Left, typename Right>
    BinaryTimesExpr < typename std::remove_reference<Left>::type,
                      typename std::remove_reference<Right>::type >
    operator*(Left&& l, Right&& r)
    {
        return {std::forward<Left>(l), std::forward<Right>(r)};
    }
    
    
    // overload to end recursion (no-op)
    template<typename Expr>
    auto Transform(const Expr& expr) -> Expr
    {
        return expr;
    }
    
    template<typename LeftLeft, typename LeftRight, typename Right>
    auto Transform(BinaryTimesExpr < BinaryTimesExpr<LeftLeft, LeftRight>,
                                     Right > const& expr)
    -> decltype(Transform(
         BinaryTimesExpr < LeftLeft,
                           BinaryTimesExpr<LeftRight, Right>
                         > {expr.left.left, {expr.left.right, expr.right}}
       ))
    {
        return Transform(
            BinaryTimesExpr < LeftLeft,
                              BinaryTimesExpr<LeftRight, Right>
                            > {expr.left.left, {expr.left.right, expr.right}}
        );
    }
    
    
    int main()
    {
        BinaryTimesExpr<int, int> beg{1,2};
        auto res = beg*3*4*5*6;
        std::cout << res << std::endl;
        std::cout << Transform(res) << std::endl;
    }
    

    输出:

    (((((1*2)*3)*4)*5)*6)

    (1*(2*(3*(4*(5*6)))))

    【讨论】:

    • 编译此代码时出现一个奇怪的编译错误:内部编译器错误:重新输入错误报告例程。无论如何,我相信在您的第二次超载时需要额外致电Transform。尝试打印 (2*3)*(4*5) 看看是否产生了 (2*(3*(4*5)))。
    • @Allan o.O 我正在使用 clang++3.4 live example
    • @Allan 是的,这个例子没有折叠,它只是“反转”了树。
    • Transform 调用需要在expr.left.leftexpr.left.rightexpr.right 上执行。
    • @Allan 我将首先尝试弄清楚如何合并完美转发。将 Transform 调用添加到其他两个部分很简单 AFAIK。
    猜你喜欢
    • 2011-04-09
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    相关资源
    最近更新 更多