【发布时间】: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