C++11 中的惰性求值

3

您知道如何执行字符串的惰性求值,就像这个D语言片段中的那样:

void log(lazy string msg) {
  static if (fooBarCondition)
    writefln(…) /* something with msg */
}

实际上,这个问题可能根本不需要懒惰模式,因为它是一个静态的if语句。也许可以在没有使用时丢弃char const*字符串?比如,在C++中:

void log(char const *msg) {
  #ifdef DEBUG
  cout << … << endl; /* something with msg */
  #else /* nothing at all */
  #endif
}

有什么想法吗?谢谢。

1
在我看来,处理这种情况的最佳方式是使用宏,以避免在不需要时构建要传递的消息。(请参见“assert”宏) - Mooing Duck
1
使用 static if 看起来很有吸引力。 - Captain Obvlious
@skp 我知道。還是很有吸引力的。 - Captain Obvlious
这是答案,我只是还没有标记它。现在已经完成了 :) - phaazon
实际上,这个问题可能根本不需要惰性求值,因为静态if语句。你似乎不理解D语言中的lazy是什么意思...它意味着,如果static_if为false,则不必计算调用表达式。在C++11中,您可以通过将lambda表达式作为参数来获得相同的效果(但不如D语言中的实现干净)。 - Jim Balter
显示剩余4条评论
3个回答

5
#ifdef DEBUG
#define log(msg) do { cout << … << endl; } while(0)
#else
#define log(msg) do { } while(0)
#endif

在C++11中实现懒惰有两种方法:宏和lambda表达式。从技术上讲,两者都不是“懒惰”的,而是所谓的“正常评估”(与“急切评估”相对),这意味着一个表达式可能会被评估多次。因此,如果你将程序从D(或Haskell)翻译成C++,你必须小心不要在这些表达式中使用具有副作用(包括计算时间)的表达式。
要实现真正的懒惰,您必须实现记忆化,这并不简单。
对于简单的日志记录,宏就足够了。

这是实现“惰性求值”的方法,除非你想用lambda表达式搞砸它。我认为你不想在日志记录中这样做。 - Elazar

3

您可以混合使用宏和Lambda表达式来实现此效果。

您可以拥有一个类型,称为lazy(延迟)。

template<class T>
class lazy {
    ...
}

然后您可以使用lambda创建一个LAZY包装器来创建其中之一

#define LAZY(E) my_lazy_type<decltype((E))>([&](){ return E; })

我的lazy_type只需要一个接受std::function的构造函数和一个重载运算符()来评估并返回它。在每次评估时,您可以将thunk替换为一个仅返回已计算值的thunk,因此它只会被计算一次。
编辑: 这是我所说的示例。然而,我想指出这不是一个完美的例子。它通过值传递了一堆东西到lazy中,可能完全打败了一开始就做这件事情的目的。它在内部使用mutable,因为我需要能够在const情况下记忆thunk。这可以以很多方式改进,但这是一个不错的概念证明。
#include <iostream>
#include <functional>
#include <memory>
#include <string>

#define LAZY(E) lazy<decltype((E))>{[&](){ return E; }}

template<class T>
class lazy {
private:
    struct wrapper {
        std::function<T()> thunk;
        wrapper(std::function<T()>&& x)
            : thunk(std::move(x)) {}
        wrapper(const std::function<T()>& x)
            : thunk(x) {}
    };
    //anytime I see mutable, I fill a bit odd
    //this seems to be warented here however
    mutable std::shared_ptr<wrapper> thunk_ptr;
public:
    lazy(std::function<T()>&& x)
        : thunk_ptr(std::make_shared<wrapper>(std::move(x))) {}
    T operator()() const {
        T val = thunk_ptr->thunk();
        thunk_ptr->thunk = [val](){return val;};
        return val;
    }
};

void log(const lazy<std::string>& msg) {
    std::cout << msg() << std::endl;
}

int main() {
    std::string hello = "hello";
    std::string world = "world";
    log(LAZY(hello + ", " + world + "!"));
    return 0;
}

谢谢,但是对于记录日志来说,这有点过头了,你不觉得吗? - phaazon
2
当然这有些过度设计了,但我是在回答如何实现“懒惰”的问题。如果你只是需要记录日志,我真的不明白为什么需要懒惰模式。 - Jake
@Jake 为什么需要“wrapper”?难道不能只使用std::shared_ptrstd::function<T()>吗? - Vahagn
1
说实话,我完全忘记了当时的想法。现在看起来很傻。我在这里写了另一个版本https://dev59.com/HmQn5IYBdhLWcg3wn4NS?lq=1,似乎在那个版本中我把它拿掉了。我记不得当时的想法了。看起来像是我的设计过程中的一个产物,直到后来才被我拿掉。 - Jake

0

虽然Elazar的答案是可行的,但我更喜欢不使用宏来实现这个(尤其是那些全部小写字母命名的宏)。 以下是我会采取的替代方案:

template<bool /* = false */>
struct logger_impl {

    template<typename T>
    static std::ostream & write(std::ostream & stream, T const &) {
        return stream;
    }
};

template<>
struct logger_impl<true> {

    template<typename T>
    static std::ostream & write(std::ostream & stream, T const & obj) {
        return stream << obj;
    }
};

template<typename T>
void log(T const & obj) {
#if defined(NDEBUG)
    logger_impl<true>::write(std::cout, obj);
#else
    logger_impl<false>::write(std::cout, obj);
#endif
}

这只是我的个人看法。


1
这个IfThenElse模板非常有用,但它不是惰性的。即使NDEBUG未定义,log(exp)内部的表达式每次都会被评估。 - Elazar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接