【问题标题】:Laziness in C++11C++11 中的懒惰
【发布时间】:2013-05-20 19:48:24
【问题描述】:

你知道如何对字符串执行惰性求值,就像在这个 D sn-p 中:

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

实际上,问题可能根本不需要懒惰,因为 static if。也许可以在不使用时丢弃char const* 字符串?比如,在 C++ 中:

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

有什么想法吗?谢谢。

【问题讨论】:

  • 执行此类 IMO 的最佳方法是宏,以避免在不需要时构建要传递的消息。 (参见assert 宏)
  • static if 的使用看起来很诱人
  • @CaptainObvlious,这是D 元编程功能
  • @skp 我知道。它仍然很诱人。
  • @CaptainObvlious:它已被提交给 C++ 标准委员会,由于一些更深层次的影响,我不确定它是否会通过。当前的 C++ 允许通过 #ifdef 进行类似的操作,但 ifdef 在预处理时进行评估,而 static if 是编译时评估(即可以使用模板、常量——不仅仅是其他定义——...

标签: c++ c++11 d lazy-evaluation


【解决方案1】:
#ifdef DEBUG
#define log(msg) do { cout << … << endl; } while(0)
#else
#define log(msg) do { } while(0)
#endif

在 C++11 中有两种实现惰性的方法:宏和 lambda 表达式。从技术上讲,两者都不是“懒惰的”,而是所谓的“正常评估”(与“急切评估”相反),这意味着一个表达式可以被评估任意多次。因此,如果您要将程序从 D(或 haskell)转换为 C++,则必须注意不要在这些表达式中使用具有副作用(包括计算时间)的表达式。

要实现真正的懒惰,你必须实现记忆,这不是那么简单。

对于简单的日志记录,宏就可以了。

【讨论】:

  • 这是获得“惰性评估”的方法,除非你想弄乱 lambdas。我认为您不想为日志记录这样做。
【解决方案2】:

您可以混合使用宏和 lambda 来创建这种效果

你可以有一个类型,懒惰的

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

然后你可以有一个 LAZY 包装器,它使用 lambda 创建其中一个

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

所有 my_lazy_type 需要的是一个接受 std::function 的构造函数,以及计算并返回 this 的 operator() 的重载。在每次评估时,您可以将 thunk 替换为仅返回已计算值的 thunk,因此它只会被计算一次。

编辑: 这是我正在谈论的一个例子。然而,我想指出,这不是一个完美的例子。它在懒惰的人身上传递了一堆价值,这可能会完全违背首先做这一切的目的。它在其中使用 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;
}

【讨论】:

  • 谢谢,但是对于记录东西来说,这有点矫枉过正,你不觉得吗?
  • 确实有点矫枉过正,我在回答如何实现懒惰的问题。如果你的目标只是记录,我真的不明白你为什么需要懒惰。
  • @Jake 为什么需要wrapper?不能只有std::shared_ptrstd::function&lt;T()&gt; 吗??
  • 我已经完全忘记了我当时的想法,说实话。现在看它看起来很傻。我在这里写了另一个版本stackoverflow.com/questions/16701108/…,似乎我在那个版本中取出了它。我不记得我在想什么。它看起来像是我的设计过程中的一个神器,直到后来我才拿出来。
【解决方案3】:

虽然 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
}

只要我的 2 美分。

【讨论】:

  • 这个 IfThenElse 模板非常有用,但它不是懒惰的。 log(exp) 中的表达式 每次都会被计算,即使 NDEBUG 没有定义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-08
相关资源
最近更新 更多