我有:
struct DoubleVec {
std::vector<double> data;
};
DoubleVec operator+(const DoubleVec& lhs, const DoubleVec& rhs) {
DoubleVec ans(lhs.size());
for(int i = 0; i < lhs.size(); ++i) {
ans[i] = lhs[i]] + rhs[i]; // assume lhs.size() == rhs.size()
}
return ans;
}
DoubleVec someFunc(DoubleVec a, DoubleVec b, DoubleVec c, DoubleVec d) {
DoubleVec ans = a + b + c + d;
}
现在,在上面,“a + b + c + d”将导致创建3个临时DoubleVec - 是否有一种方法可以通过某种类型的模板魔法来优化它...即优化它等同于:
DoubleVec ans(a.size());
for(int i = 0; i < ans.size(); i++) ans[i] = a[i] + b[i] + c[i] + d[i];
您可以假设所有DoubleVec都具有相同的元素数。
高层次的想法是在“+”上做一些模板魔术,“延迟计算”直到=,此时它会自我调查,嗯...我只是添加了数字,合成a [i] + b [i] + c [i] + d [i] ...而不是所有的临时数。
谢谢!
答案 0 :(得分:14)
是的,这正是表达模板(例如,请参阅http://www.drdobbs.com/184401627或http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Expression-template)。
这个想法是让operator+
返回某种代表对象,代表要评估的表达式树。然后编写operator=
来获取这样的表达式树并立即对其进行全面评估,避免创建临时值,并应用可能适用的任何其他优化。
答案 1 :(得分:2)
查看Boost.Proto,它是一个直接在C ++中编写EDSL(嵌入式域特定语言)的库。甚至还有example显示您需要的内容。
答案 2 :(得分:1)
如果我们不得不将Blitz ++解决的问题归结为单句,我们会说,“对于任何有趣的计算,数组数学的天真实现都是非常低效的。”要明白我们的意思,请采取无聊的陈述
x = a + b + c;
这里的问题是上面的操作符+签名太贪婪了:它试图尽快评估a + b,而不是等到整个表达式,包括添加c,都可用。
在表达式的解析树中,评估从叶子开始,然后向上进行到根。这里需要的是一种延迟评估的方法,直到库具有所有表达式的部分:即,直到赋值运算符被执行。 Blitz ++采取的策略是为整个表达式构建编译器解析树的副本,允许它从上到下管理评估
这不能是任何普通的解析树:因为数组表达式可能涉及其他操作,如乘法,这需要自己的评估策略,并且因为表达式可以任意大和嵌套,所以使用节点和指针构建的解析树必须由Blitz ++评估引擎在运行时遍历以发现其结构,从而限制性能。此外,Blitz ++必须使用某种运行时调度来处理不同的操作类型组合,这再次限制了性能。
相反,Blitz ++使用表达式模板构建编译时解析树。以下是它的工作原理:运算符只是在Expression实例中打包对其参数的引用,而不是返回一个新计算的数组,标记为操作:
// operation tags
struct plus; struct minus;
// expression tree node
template <class L, class OpTag, class R>
struct Expression
{
Expression(L const& l, R const& r)
: l(l), r(r) {}
float operator[](unsigned index) const;
L const& l;
R const& r;
};
// addition operator
template <class L, class R>
Expression<L,plus,R> operator+(L const& l, R const& r)
{
return Expression<L,plus,R>(l, r);
}
请注意,当我们编写一个+ b时,我们仍然拥有在Expression类型中进行编码所需的所有信息,并且可以通过表达式的存储引用访问数据。当我们写一个+ b + c时,我们得到一个类型的结果:
Expression<Expression<Array,plus,Array>,plus,Array>