在C ++中进行懒惰评估

C ++没有对懒惰评估的本机支持(就像Haskell一样)。

我想知道是否有可能以合理的方式在C ++中实现懒惰评估。 如果是的话,你会怎么做?

编辑:我喜欢康拉德鲁道夫的答案。

我想知道是否有可能以更通用的方式实现它,例如通过使用一个参数化的类惰性,本质上适用于matrix_add为matrix工作的方式。

T上的任何操作都会返回。 唯一的问题是将参数和操作代码存储在懒本身中。 任何人都可以看到如何改善呢?

我想知道是否有可能以合理的方式在C ++中实现懒惰评估。 如果是的话,你会怎么做?

是的,这是可能的,并且经常进行,例如matrix计算。 促成这一点的主要机制是运营商超载。 考虑matrix加法的情况。 函数的签名通常看起来像这样:

matrix operator +(matrix const& a, matrix const& b); 

现在,为了使这个函数懒惰,只需返回一个代理而不是实际的结果即可:

 struct matrix_add; matrix_add operator +(matrix const& a, matrix const& b) { return matrix_add(a, b); } 

现在所有需要做的就是编写这个代理:

 struct matrix_add { matrix_add(matrix const& a, matrix const& b) : a(a), b(b) { } operator matrix() const { matrix result; // Do the addition. return result; } private: matrix const& a, b; }; 

魔术在于方法operator matrix() ,它是从matrix_add到纯matrix的隐式转换运算符。 这样,你可以链接多个操作(当然提供适当的重载)。 仅当最终结果被分配给matrix实例时才进行评估。

编辑我应该更加明确。 实际上,代码没有意义,因为尽pipe评估发生在懒惰的地方,但它仍然发生在同一个expression式中。 特别的是,除非matrix_add结构被改变以允许链接添加,否则另外的添加将评估这个代码。 C ++ 0x通过允许可变参数模板(即可变长度的模板列表)极大地方便了这一点。

然而,这个代码实际上有一个真正的直接好处的一个非常简单的情况是:

 int value = (A + B)(2, 3); 

这里,假定AB是二维matrix,并且用Fortran符号完成解引用,即,上面计算matrix和中的一个元素。 添加整个matrix当然是浪费的。 matrix_add救援:

 struct matrix_add { // … yadda, yadda, yadda … int operator ()(unsigned int x, unsigned int y) { // Calculate *just one* element: return a(x, y) + b(x, y); } }; 

其他例子比比皆是。 我只记得不久前我已经实现了一些相关的东西。 基本上,我必须实现一个string类,它应该坚持一个固定的,预先定义的接口。 然而,我的特殊的string类处理了实际上没有存储在内存中的巨大的string。 通常,用户只能使用函数infix从原始string中访问小的子串。 我为我的stringtypes重载了这个函数,返回一个包含我的string引用的代理以及所需的开始和结束位置。 只有当这个子string被实际使用时,它才会查询一个C API来检索这部分string。

Boost.Lambda非常好,但是Boost.Proto 正是你正在寻找的。 它已经有所有 C ++操作符的重载,当调用proto::eval()时默认执行它们通常的function,但是可以改变。

Konrad已经解释的可以进一步支持操作符的嵌套调用,所有这些都是懒惰地执行的。 在Konrad的例子中,他有一个expression式对象,可以存储两个参数,一个操作的两个操作数。 问题是它只会懒惰的执行一个子expression式,很好地解释了简单的懒惰评估的概念,但是并没有大幅提高性能。 另一个例子也很好地显示了如何应用operator()来只添加一些使用该expression式对象的元素。 但是为了评估任意复杂的expression式,我们需要一些可以存储这个结构的机制。 我们无法绕过模板来做到这一点。 而这个名字就是expression templates 。 这个想法是,一个模板化的expression式对象可以recursion地存储一些任意的子expression式的结构,就像一棵树,操作是节点,操作数是子节点。 对于我刚刚发现的一个非常好的解释(在我写下面的代码后几天),请看这里 。

 template<typename Lhs, typename Rhs> struct AddOp { Lhs const& lhs; Rhs const& rhs; AddOp(Lhs const& lhs, Rhs const& rhs):lhs(lhs), rhs(rhs) { // empty body } Lhs const& get_lhs() const { return lhs; } Rhs const& get_rhs() const { return rhs; } }; 

这将存储任何加法操作,甚至嵌套一个操作,正如下面的operator +定义可以看到的那样:

 struct Point { int x, y; }; // add expression template with point at the right template<typename Lhs, typename Rhs> AddOp<AddOp<Lhs, Rhs>, Point> operator+(AddOp<Lhs, Rhs> const& lhs, Point const& p) { return AddOp<AddOp<Lhs, Rhs>, Point>(lhs, p); } // add expression template with point at the left template<typename Lhs, typename Rhs> AddOp< Point, AddOp<Lhs, Rhs> > operator+(Point const& p, AddOp<Lhs, Rhs> const& rhs) { return AddOp< Point, AddOp<Lhs, Rhs> >(p, rhs); } // add two points, yield a expression template AddOp< Point, Point > operator+(Point const& lhs, Point const& rhs) { return AddOp<Point, Point>(lhs, rhs); } 

现在,如果你有

 Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 }; p1 + (p2 + p3); // returns AddOp< Point, AddOp<Point, Point> > 

您现在只需要重载operator =并为Pointtypes添加一个合适的构造函数并接受AddOp。 将其定义更改为:

 struct Point { int x, y; Point(int x = 0, int y = 0):x(x), y(y) { } template<typename Lhs, typename Rhs> Point(AddOp<Lhs, Rhs> const& op) { x = op.get_x(); y = op.get_y(); } template<typename Lhs, typename Rhs> Point& operator=(AddOp<Lhs, Rhs> const& op) { x = op.get_x(); y = op.get_y(); return *this; } int get_x() const { return x; } int get_y() const { return y; } }; 

并将相应的get_x和get_y作为成员函数添加到AddOp中:

 int get_x() const { return lhs.get_x() + rhs.get_x(); } int get_y() const { return lhs.get_y() + rhs.get_y(); } 

注意我们怎么没有创build任何Pointtypes的临时对象。 这可能是一个有很多领域的大matrix。 但是在需要的时候,我们懒懒的计算。

我没有什么补充Konrad的职位,但你可以看看Eigen一个懒惰的评估正确的例子,在一个真实的世界的应用程序。 这是相当令人鼓舞的。

C + + 0x是好的,所有….但对于我们这些生活在当下你有Boost lambda库和Boost Phoenix。 这两者的目的都是为C ++带来大量的函数式编程。

我正在考虑实现一个使用std::function的模板类。 这个class或多或less应该是这样的:

 template <typename Value> class Lazy { public: Lazy(std::function<Value()> function) : _function(function), _evaluated(false) {} Value &operator*() { Evaluate(); return _value; } Value *operator->() { Evaluate(); return &_value; } private: void Evaluate() { if (!_evaluated) { _value = _function(); _evaluated = true; } } std::function<Value()> _function; Value _value; bool _evaluated; }; 

例如用法:

 class Noisy { public: Noisy(int i = 0) : _i(i) { std::cout << "Noisy(" << _i << ")" << std::endl; } Noisy(const Noisy &that) : _i(that._i) { std::cout << "Noisy(const Noisy &)" << std::endl; } ~Noisy() { std::cout << "~Noisy(" << _i << ")" << std::endl; } void MakeNoise() { std::cout << "MakeNoise(" << _i << ")" << std::endl; } private: int _i; }; int main() { Lazy<Noisy> n = [] () { return Noisy(10); }; std::cout << "about to make noise" << std::endl; n->MakeNoise(); (*n).MakeNoise(); auto &nn = *n; nn.MakeNoise(); } 

上面的代码应该在控制台上产生以下消息:

 Noisy(0) about to make noise Noisy(10) ~Noisy(10) MakeNoise(10) MakeNoise(10) MakeNoise(10) ~Noisy(10) 

请注意,打印Noisy(10)的构造函数在访问variables之前不会被调用。

尽pipe如此,这门课并不完美。 第一件事就是默认构造函数的Value将不得不被调用成员初始化(在这种情况下打印Noisy(0) )。 我们可以使用指针来代替_value ,但我不确定是否会影响性能。

一切皆有可能。

这取决于你的意思:

 class X { public: static X& getObjectA() { static X instanceA; return instanceA; } }; 

在这里我们有一个全局variables的影响,这个variables在第一次使用的时候被懒惰地评估过了。

正如问题中新提出的要求。
并且偷了Konrad Rudolph的devise并将其延伸。

懒惰对象:

 template<typename O,typename T1,typename T2> struct Lazy { Lazy(T1 const& l,T2 const& r) :lhs(l),rhs(r) {} typedef typename O::Result Result; operator Result() const { O op; return op(lhs,rhs); } private: T1 const& lhs; T2 const& rhs; }; 

如何使用它:

 namespace M { class Matrix { }; struct MatrixAdd { typedef Matrix Result; Result operator()(Matrix const& lhs,Matrix const& rhs) const { Result r; return r; } }; struct MatrixSub { typedef Matrix Result; Result operator()(Matrix const& lhs,Matrix const& rhs) const { Result r; return r; } }; template<typename T1,typename T2> Lazy<MatrixAdd,T1,T2> operator+(T1 const& lhs,T2 const& rhs) { return Lazy<MatrixAdd,T1,T2>(lhs,rhs); } template<typename T1,typename T2> Lazy<MatrixSub,T1,T2> operator-(T1 const& lhs,T2 const& rhs) { return Lazy<MatrixSub,T1,T2>(lhs,rhs); } } 

约翰内斯的答案是有效的。但是当涉及到更多的括号时,它不能如愿。 这是一个例子。

 Point p1 = { 1, 2 }, p2 = { 3, 4 }, p3 = { 5, 6 }, p4 = { 7, 8 }; (p1 + p2) + (p3+p4)// it works ,but not lazy enough 

因为三个超负荷的+运营商没有覆盖案件

 AddOp<Llhs,Lrhs>+AddOp<Rlhs,Rrhs> 

所以编译器必须将(p1 + p2)或(p3 + p4)转换为Point,这并不够懒惰。当编译器决定要转换的时候,它会抱怨。 因为没有一个比另一个好。 这里是我的扩展:添加另一个重载的运算符+

  template <typename LLhs, typename LRhs, typename RLhs, typename RRhs> AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>> operator+(const AddOp<LLhs, LRhs> & leftOperandconst, const AddOp<RLhs, RRhs> & rightOperand) { return AddOp<AddOp<LLhs, LRhs>, AddOp<RLhs, RRhs>>(leftOperandconst, rightOperand); } 

现在,编译器可以正确处理上面的情况,并且不需要隐式转换,volia!

由于它将在C ++ 0x中由lambdaexpression式完成。

在C ++ 11懒惰评价类似于hiapay的答案可以使用std :: shared_future来实现。 你仍然需要将计算封装在lambdas里,但是memoization是照顾的:

 std::shared_future<int> a = std::async(std::launch::deferred, [](){ return 1+1; }); 

这是一个完整的例子:

 #include <iostream> #include <future> #define LAZY(EXPR, ...) std::async(std::launch::deferred, [__VA_ARGS__](){ std::cout << "evaluating "#EXPR << std::endl; return EXPR; }) int main() { std::shared_future<int> f1 = LAZY(8); std::shared_future<int> f2 = LAZY(2); std::shared_future<int> f3 = LAZY(f1.get() * f2.get(), f1, f2); std::cout << "f3 = " << f3.get() << std::endl; std::cout << "f2 = " << f2.get() << std::endl; std::cout << "f1 = " << f1.get() << std::endl; return 0; } 

创build自己的“容器”类很容易,该类使用生成函数对象并公开迭代器。