I asked a similar question: say I have a predicate auto p1 = [](int x){ return x > 2; }
and a predicate auto p2 = [](int x){ return x < 6; }
, how do I combine p1
and p2
to obtain p1and2
such that p1and2(x) == p1(x) && p2(x)
? The answer was use boost::hana::demux
(refer to the linked question for details).
Sometimes, however, the evaluation of one predicate should occur only if the other predicate evaluates to a given truthness value, e.g. true
.
For instance one predicate might be
constexpr auto has_value = [](std::optional<int> opt){ return opt.has_value(); };
and the other predicate
constexpr auto has_positive = [](std::optional<int> opt){ return opt.value() == 3; };
It's easy to recognize the following
bool b1 = has_value(some_opt_int) && has_positive(some_opt_int); // true or false, but just fine either way
bool b2 = has_positive(some_opt_int) && has_value(some_opt_int); // runtime error if !some_opt_int.has_value()
Defining
constexpr auto all = boost::hana::demux([](auto const&... x) { return (x && ...); });
and using it like so
std::optional<int> empty{};
contexpr auto has_value_which_is_positive = all(has_value, has_positive);
bool result = has_value_which_is_positive(empty);
would lead to failure at run time, because the it's the function call to the variadic generic lambda wrapped in all
which forces the evaluation of its arguments, not the fold expression (x && ...)
.
So my question is, how do I combine has_value
and has_positive
to get has_value_which_is_positive
? More in general, how do I "and" together several predicates such that they're evaluated only as much as the short-circuit mechanism requires?
I think that, in order to prevent the predicates from being evaluated, I can have wrap them in some function objects which, when applied to the argument (the std::optional
), give back another object which wraps the predicate and the std::optional
together, and has an operator bool
conversion function which would be triggered only when the fold expression is evaluated.
This is my attempt, which is miserably undefined behavior because the assertion in main
sometimes fails and sometimes doesn't:
#include <optional>
#include <boost/hana/functional/demux.hpp>
#include <boost/hana/functional/curry.hpp>
template<typename P, typename T>
struct LazilyAppliedPred {
LazilyAppliedPred(P const& p, T const& t)
: p(p)
, t(t)
{}
P const& p;
T const& t;
operator bool() const {
return p(t);
}
};
constexpr auto lazily_applied_pred = [](auto const& p, auto const& t) {
return LazilyAppliedPred(p,t);
};
auto constexpr lazily_applied_pred_curried = boost::hana::curry<2>(lazily_applied_pred);
constexpr auto all_true = [](auto const&... x) { return (x && ...); };
constexpr auto all = boost::hana::demux(all_true);
constexpr auto has_value = [](std::optional<int> o){
return o.has_value();
};
constexpr auto has_positive = [](std::optional<int> o){
assert(o.has_value());
return o.value() > 0;
};
int main() {
assert(all(lazily_applied_pred_curried(has_value),
lazily_applied_pred_curried(has_positive))(std::optional<int>{2}));
}
I've just realized that an ad-hoc lambda to do what I described is actually very terse:
#include <assert.h>
#include <optional>
constexpr auto all = [](auto const& ... predicates){
return [&predicates...](auto const& x){
return (predicates(x) && ...);
};
};
constexpr auto has_value = [](std::optional<int> o){
return o.has_value();
};
constexpr auto has_positive = [](std::optional<int> o){
assert(o.has_value());
return o.value() > 0;
};
int main() {
assert(all(has_value, has_positive)(std::optional<int>{2}));
assert(!all(has_value, has_positive)(std::optional<int>{}));
}
However, there's still something about short-circuiting in fold expressions that doesn't convince me...