介绍
我正在尝试了解表达式模板,因为它似乎是一种适用于各种计算的非常强大的技术。我在网上查看了不同的例子(例如维基百科 https://en.wikipedia.org/wiki/Expression_templates),我编写了一堆执行不同计算的小程序。然而,我发现了一个无法解决的问题;即,将任意表达式分配给变量、其“惰性求值”以及将其重新分配给另一个任意表达式。
一个可以用auto
,为了将一个表达式分配给一个变量,但是重新分配给另一个表达式是不可能的(你可以看看整个库sadET https://github.com/dkaramit/sadET/tree/simplificationRules,以便充分理解我正在尝试做的事情)。此外,分配和重新分配可以使用CRTP https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern和超载operator=
。然而,表达式是在赋值过程中计算的,我们基本上丢失了关于表达式是什么的所有信息。
因此,我尝试使用多态克隆+CRTP(例如参见this https://katyscode.wordpress.com/2013/08/22/c-polymorphic-cloning-and-the-crtp-curiously-recurring-template-pattern/),这种方法有效,但是当我尝试将变量重新分配给包含相同变量的表达式时,出现段错误。
Code
在下面的代码中,我展示了如何实现加法表达式模板的简化版本。在下面的代码中,msg
是一个宏std::cout<<typeid(*this).name()<<std::endl;
,以便跟踪正在评估的内容。
这是所有表达式的(纯)基类,它允许我将变量分配给通用表达式(使用多态克隆):
struct BaseExpression{
BaseExpression()=default;
virtual double evaluate()const =0 ;
};
这是一个所有表达式都继承自的类,并且允许我使用 CRTP
template<typename subExpr>
struct GenericExpression:BaseExpression{
const subExpr& self() const {return static_cast<const subExpr&>(*this);}
subExpr& self() {return static_cast<subExpr&>(*this);}
double evaluate() const { msg; return self().evaluate(); };
};
这个想法是所有表达式都由数字、基本函数和运算符组成。所以,我写了一个Number
类如下
class Number: public GenericExpression<Number>{
double val;
public:
Number()=default;
Number(const double &x):val(x){}
Number(const Number &x):val(x.evaluate()){}
double evaluate()const { msg; return val;}
double& evaluate() { msg; return val;}
};
遵循表达式模板的思想,那么,相加就是
template<typename leftHand,typename rightHand>
class Addition:public GenericExpression<Addition<leftHand,rightHand>>{
const leftHand &LH;
const rightHand &RH;
public:
Addition(const leftHand &LH, const rightHand &RH):LH(LH),RH(RH){}
double evaluate() const {msg; return LH.evaluate() + RH.evaluate();}
};
template<typename leftHand,typename rightHand>
Addition<leftHand,rightHand>
operator+(const GenericExpression<leftHand> &LH, const GenericExpression<rightHand> &RH){
return Addition<leftHand,rightHand>(LH.self(),RH.self());
}
为了能够利用BaseExpression
,我还写了一个Expression
将用于分配实例的类GenericExpression
to Expression
变量。
class Expression: public GenericExpression<Expression>{
public:
BaseExpression *baseExpr;
Expression()=default;
Expression(const Expression &E){baseExpr = E.baseExpr;};
double evaluate() const {msg; return baseExpr->evaluate();}
template<typename subExpr>
void assign(const GenericExpression<subExpr> &RH){
baseExpr = new subExpr(RH.self());
}
};
我这个类,重要的一点是指针baseExpr
允许改变evaluate
函数到GenericExpression
当我们调用分配时。
一些例子
为了测试这是否有效,我声明了以下变量:
Number x(3.2);
Number y(-2.3);
Expression z,w;
然后,我们可以看到以下事情起作用了
//assignment to Number
z.assign(x);
cout<<z.evaluate()<<endl;
//assignment to Addition<Number,Number>
z.assign(x+y);
cout<<z.evaluate()<<endl;
//assignment to Addition<Expression,Number>
w.assign(z+y);
cout<<w.evaluate()<<endl;
但是,当我执行以下操作时,运行时会出现无限递归z.evaluate()
since z.baseExpr
指向自身。
// Segmentation fault of z.evaluate() due to infinite recursion between
// LH.evaluate() (in Addition<Expression,Number>::evaluate()) and
// baseExpr->evaluate() (in Expression::evaluate())
z.assign(z+x);
cout<<z.evaluate()<<endl;
重现我描述的行为的整个代码:
#include<iostream>
#include<cmath>
// message to print during evaluation, in order to track the evaluation path.
#define msg std::cout<<typeid(*this).name()<<std::endl;
struct BaseExpression{
BaseExpression()=default;
virtual double evaluate()const =0 ;
};
template<typename subExpr>
struct GenericExpression:BaseExpression{
const subExpr& self() const {return static_cast<const subExpr&>(*this);}
subExpr& self() {return static_cast<subExpr&>(*this);}
double evaluate() const { msg; return self().evaluate(); };
};
class Number: public GenericExpression<Number>{
double val;
public:
Number()=default;
Number(const double &x):val(x){}
Number(const Number &x):val(x.evaluate()){}
double evaluate()const { msg; return val;}
double& evaluate() { msg; return val;}
};
template<typename leftHand,typename rightHand>
class Addition:public GenericExpression<Addition<leftHand,rightHand>>{
const leftHand &LH;
const rightHand &RH;
public:
Addition(const leftHand &LH, const rightHand &RH):LH(LH),RH(RH){}
double evaluate() const {msg; return LH.evaluate() + RH.evaluate();}
};
template<typename leftHand,typename rightHand>
Addition<leftHand,rightHand>
operator+(const GenericExpression<leftHand> &LH, const GenericExpression<rightHand> &RH){
return Addition<leftHand,rightHand>(LH.self(),RH.self());
}
class Expression: public GenericExpression<Expression>{
public:
BaseExpression *baseExpr;
Expression()=default;
Expression(const Expression &E){baseExpr = E.baseExpr;};
// Expression(Expression *E){baseExpr = E->baseExpr;};
double evaluate() const {msg; return baseExpr->evaluate();}
template<typename subExpr>
void assign(const GenericExpression<subExpr> &RH){
baseExpr = new subExpr(RH.self());
}
};
using std::cout;
using std::endl;
int main(){
Number x(3.2);
Number y(-2.3);
Expression z,w;
// works fine!
z.assign(x);
cout<<z.evaluate()<<endl;
// works fine!
z.assign(x+y);
cout<<z.evaluate()<<endl;
// works fine!
w.assign(z+y);
cout<<w.evaluate()<<endl;
// Segmentation fault of z.evaluate() infinite recursion in between
// LH.evaluate() (in Addition<Expression,Number>::evaluate()) and
// baseExpr->evaluate() (in Expression::evaluate())
z.assign(z+x);
cout<<z.evaluate()<<endl;
return 0;
}
转向自动差异
自动微分的实现与上面的例子没有什么不同。最重要的变化是引入了Constant
类和一个derivative
派生自的所有类的成员函数GenericExpression
,返回任意表达式。也就是说,我们添加
class Constant: public GenericExpression<Constant>{
double val;
public:
Constant()=default;
Constant(const double &x):val(x){}
Constant(const Constant &x):val(x.evaluate()){}
double evaluate()const { msg; return val;}
double& evaluate() { msg; return val;}
auto derivative(){return Constant(0);}
};
The derivative
其他类中的成员应如下所示:
auto Number::derivative(){return Constant(1);}
template<typename leftHand,typename rightHand>
auto Addition<left,right>::derivative(){return LH.derivative() + RH.derivative();}
问题
即使我们找到一种方法来避免(或忽略)我之前展示的段错误,我也看不出有什么办法可以使derivative
的虚拟成员函数BaseExpression
,因为它返回一种不同类型的表达式,原则上,我们只知道何时调用它(因此auto
).
最后一个问题
在我试图描述的上下文中,有什么办法可以做到以下几点
Number x(5);
Expression z;
z.assign(x);
z.assign(z+x);
cout<<z.evaluate()<<endl;
cout<<z.derivative().evaluate()<<endl;
最好没有段错误?
我将感谢社区的意见或见解!
Edit
我简化了代码,以使事情变得更容易,并避免潜在的危险指针。
在这个例子中,我创建了成员函数evaluate
指向 lambda 的函数指针。这样,如果我理解正确的话,我直接复制...::evaluate
to Expression::evaluate
。然而,我仍然遇到段错误......
奇怪的是,当使用递归函数对向量求和时,我遇到了段错误。
新代码:
#include<iostream>
#include<functional>
#include<vector>
// message to print during evaluation, in order to track the evaluation path.
#define msg std::cout<<typeid(*this).name()<<std::endl
class Expression{
public:
std::function<double(void)> evaluate;
template<typename T>
Expression(const T &RH){evaluate = RH.evaluate;}
template<typename T>
Expression& operator=(const T &RH){evaluate = RH.evaluate; return *this;}
};
class Number{
double val;
public:
Number()=default;
std::function<double()> evaluate;
Number(const double &x):val(x){ evaluate = [this](){msg; return this->val;}; }
Number(const Number &x):val(x.evaluate()){ evaluate = [this](){msg; return this->val;}; }
};
template<typename leftHand,typename rightHand>
class Addition{
const leftHand &LH;
const rightHand &RH;
public:
std::function<double()> evaluate;
Addition(const leftHand &LH, const rightHand &RH):LH(LH),RH(RH)
{evaluate = [this](){msg; return this->LH.evaluate() + this->RH.evaluate();};}
};
template<typename leftHand,typename rightHand>
auto operator+(const leftHand &LH, const rightHand &RH){return Addition<leftHand,rightHand>(LH,RH); }
using std::cout;
using std::endl;
inline Expression func (std::vector<Expression> x, int i){
cout<<i<<endl;
if (i==0){return static_cast<Expression>(x[0]);}
return static_cast<Expression>( x[i] + func(x,i-1) ) ;
};
int main(){
Number y(-2.);
Number x(1.33);
Expression z(y+x);
Expression w(x+y+x);
// works
z =x;
cout<<z.evaluate()<<endl;
cout<<(z+z+z+z).evaluate()<<endl;
// Segfault due to recusion
// z =z+x;
// cout<<z.evaluate()<<endl;
// Unkown Segfault
// z = x+y ;
// cout<<(z+z).evaluate()<<endl;
// cout<<typeid(z+z).name()<<endl;
// Unkown Segfault
// z = w+y+x+x;
// cout<<z.evaluate()<<endl;
// Unkown Segfault
// std::vector<Expression> X={x,y,x,y,x,y,x,y};
// cout << typeid(func(X,X.size()-1)).name() << endl;
// cout << (func(X,X.size()-1)).evaluate() << endl;
return 0;
}
Update
大约一年后,我重新审视这个问题,我想我已经找到了解决方案;使用纯粹的抽象类,每个表达式都从中派生。那么一切(从变量,到运算符的返回类型和函数)都是这个抽象类的指针。我目前正在处理的代码是here https://github.com/dkaramit/sadCpp。但请注意,我没有在新设计中使用表达式模板,但我希望在不久的将来的某个时候这样做。