patterncppMinor
Optionally Lazy Parameters
Viewed 0 times
parameterslazyoptionally
Problem
Inspired by Swift's
The
I provide a macro,
``
// sense. See https://stackoverflow.com/q/38214138/1858225
// In general, not making the destructor virtual
@autoclosure feature, I tried writing a brief C++-14 header that permits "optionally" lazy parameters (by "lazy" I mean @autoclosure-like; I chose the word "lazy" in emulation of lazy evaluation in languages such as Haskell). From this post on interesting Swift features:The
@autoclosure attribute delays the execution of a function that's activated in a function parameter. Essentially, calling a function inside of a parameter will wrap the function call in a closure for later use in the function body.I provide a macro,
LAZY_PARAM, that can be applied to parameters in function declarations; when calling these functions, arguments must be supplied using either the LAZY_ARG macro, which uses reference-based closure-semantics to delay evaluation of the expression, or the EAGER_ARG macro, which immediately evaluates the expression and wraps it in a light-weight class that simply stores the result. It's copied below and also available here. (NOTE: The linked version, on GitHub, will be kept up-to-date as I make changes, but the copied version may not.)``
#pragma once
#include
#include
// Uses dynamic dispatch to permit lazy OR eager argument evaluation at the
// caller's discretion, with uniform access to the final value from the callee's
// perspective.
template
class LazyType_Base
{
protected:
// For LazyType_TrueLazy
template
LazyType_Base(CALLABLE&& expr)
{
static_assert(
std::is_same::type, VAL_TYPE
>::value,
"Expression does not evaluate to correct type!");
}
// For LazyType_Eager
// =default is not permitted by GCC here; see
// https://stackoverflow.com/q/38213809/1858225
LazyType_Base(void) {}
public:
// Both compilers suddenly fail when this is introduced, attempting to
// instantiate LazyType_Base>`, which makes no// sense. See https://stackoverflow.com/q/38214138/1858225
// In general, not making the destructor virtual
Solution
Does this seem like a reasonable idea for a lightweight library? Is there anything similar that's already available? I like the idea of it for functions that may or may not require actually using particular arguments, e.g. in a logger with a "sensitivity level"
Well, passing parameters lazily sounds like a good idea; but I don't really see why the callee needs to be aware of both "lazy" and "non-lazy" params. In other words, I would reimplement your whole library as three lines:
and then force the callee to be implemented as
(notice the one extra set of parentheses in
Furthermore, I would offer the caller the option to lazily compute the value on first reference and then cache that value for all future references:
You certainly can reinvent-the-wheel of
Implicit conversions (e.g. your
Raw rvalue references (e.g. your
Your
Well, passing parameters lazily sounds like a good idea; but I don't really see why the callee needs to be aware of both "lazy" and "non-lazy" params. In other words, I would reimplement your whole library as three lines:
#define OPTIONALLY_LAZY(T) std::function
#define LAZY_ARG(e) [&](){ return e; }
#define EAGER_ARG(e) [_x=(e)](){ return _x; }and then force the callee to be implemented as
void PermitLazy(
OPTIONALLY_LAZY(int) my_int)
{
std::cout << "Called 'PermitLazy'." << std::endl;
std::cout << "Got possibly-lazy int: " << my_int() << std::endl;
}(notice the one extra set of parentheses in
my_int() there).Furthermore, I would offer the caller the option to lazily compute the value on first reference and then cache that value for all future references:
#define LAZY_MEMOIZED_ARG(e) \
[&, _first=true, _x=decltype(e){}]() mutable { \
if (_first) { _x = (e); _first = false; } \
return e; \
}You certainly can reinvent-the-wheel of
std::function while you're at it, but you don't need to reinvent it. The standard one will probably be faster than your thing.Implicit conversions (e.g. your
operator T()) are the devil and should be avoided. For example, consider the semantics of std::max(my_int, 0) with your library (and compare to the semantics of std::max(my_int(), 0) with my three-liner). Also consider what happens if you pass your my_int to a function expecting a const int&. (Off the top of your head, what does it do? What should it do? Now try it — what does it really do?)Raw rvalue references (e.g. your
LazyType_Base&&) are also a code smell to be avoided; in these cases, if you can't take by const lvalue reference, you usually ought to be taking by value.Your
&->auto {...} is just a very long-winded way of writing [&]{...}. Personally I do write [&](){...} with the "unnecessary" parentheses, and wouldn't dock you for those; but writing out (void) in C++, or writing ->auto at all, is definitely unidiomatic.Code Snippets
#define OPTIONALLY_LAZY(T) std::function<T()>
#define LAZY_ARG(e) [&](){ return e; }
#define EAGER_ARG(e) [_x=(e)](){ return _x; }void PermitLazy(
OPTIONALLY_LAZY(int) my_int)
{
std::cout << "Called 'PermitLazy'." << std::endl;
std::cout << "Got possibly-lazy int: " << my_int() << std::endl;
}#define LAZY_MEMOIZED_ARG(e) \
[&, _first=true, _x=decltype(e){}]() mutable { \
if (_first) { _x = (e); _first = false; } \
return e; \
}Context
StackExchange Code Review Q#133991, answer score: 2
Revisions (0)
No revisions yet.