简体   繁体   中英

Laziness in C++11

Do you know how to perform a lazy evaluation of string, like in this D snippet:

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

Actually, the problem might not need laziness at all since the static if. Maybe it's possible to discard char const* strings when not used? Like, in C++:

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

Any idea? Thank you.

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

There are two ways to achieve laziness in C++11: macros and lambda expressions. Both are not "lazy" technically, but what is called "normal evaluation" (as opposed to "eager evaluation"), which mean that an expression might be evaluated any number of times. So if you are translating a program from D (or haskell) to C++ you will have to be careful not to use expressions with side effects (including computation time) in these expressions.

To achieve true laziness, you will have to implement memoizing, which is not that simple.

For simple logging, macros are just fine.

You could mix macros and lambdas to create this effect

you could have a type, lazy

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

and then you could have a LAZY wrapper that created one of these using a lambda

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

All my_lazy_type needs is a constructor that accepts a std::function, and a an overload of operator() that evaluates and returns this. On each evaluation you can replace the thunk with a thunk that just returns the already computed value and thus it would only get computed once.

edit: here is an example of what I am talking about. I would like however to point out that this is not a perfect example. it passes around a bunch of stuff by value in side the lazy which may completely defeat the purpose of doing this all in the first place. It uses mutable inside this because I need to be able to memoize the thunk in const cases. This could be improved in a lot of ways but it's a decent proof of concept.

#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;
}

While Elazar's answer works, I prefer not to use macros for this (especially not ones with all-lowercase names). Here is what I would do instead:

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
}

Just my 2 cents.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM