以函数方式组合谓词,并允许短路



前言

我问了一个类似的问题:假设我有一个谓词auto p1 = [](int x){ return x > 2; }和一个谓词auto p2 = [](int x){ return x < 6; },我如何将p1p2组合得到p1and2,使得p1and2(x) == p1(x) && p2(x)?答案是使用boost::hana::demux(有关详细信息,请参阅链接的问题)。

新问题和问题

然而,有时,只有当另一个谓词的求值结果为给定的真实性值时,才应该对一个谓词进行求值,例如true

例如,一个谓词可能是

constexpr auto has_value = [](std::optional<int> opt){ return opt.has_value(); };

和另一个谓词

constexpr auto has_positive = [](std::optional<int> opt){ return opt.value() == 3; };

很容易识别以下

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()

定义

constexpr auto all = boost::hana::demux([](auto const&... x) { return (x && ...); });

像这样使用

std::optional<int> empty{};
contexpr auto has_value_which_is_positive = all(has_value, has_positive);
bool result = has_value_which_is_positive(empty);

会在运行时导致失败,因为它是对封装在all中的可变泛型lambda的函数调用,它强制计算其参数,而不是折叠表达式(x && ...)

所以我的问题是,如何将has_valuehas_positive组合得到has_value_which_is_positive?更一般地;以及";把几个谓词放在一起,这样它们的评估就只能满足短路机制的要求吗?

我的尝试

我认为,为了防止谓词被求值,我可以将它们封装在一些函数对象中,当应用于参数(std::optional)时,这些对象会返回另一个对象,该对象将谓词和std::optional封装在一起,并且具有operator bool转换函数,该函数只有在对fold表达式求值时才会触发。

这是我的尝试,因为main中的断言有时失败,有时没有:

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

(后续问题。)

我刚刚意识到,用一个特殊的lambda来做我所描述的事情实际上非常简洁:

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

然而,折叠表达中的短路仍然有一些东西不能说服我…

也许你的问题比我所能理解的更微妙,但这个非常简单的解决方案似乎奏效了(据我所知)。

/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp 
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion 
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
#include <optional>
template<typename Predicate1,
typename Predicate2>
auto
lazy_and(Predicate1 p1,
Predicate2 p2)
{
return [&](const auto &arg)
{
return p1(arg)&&p2(arg);
};
}
int
main()
{
// basic predicates
const auto has_value=[&](const auto &opt){ return opt.has_value(); };
const auto is_positive=[&](const auto &opt){ return opt.value()>0; };
// combination of predicates
const auto has_value_and_is_positive=lazy_and(has_value, is_positive);
const auto is_positive_and_has_value=lazy_and(is_positive, has_value);
// test
const auto test_predicate=
[&](const auto &title, const auto &predicate, const auto &arg)
{
try
{
const auto result=predicate(arg);
std::cout << title << " --> " << result << 'n';
}
catch(const std::exception &e)
{
std::cerr << title << " !!! " << e.what() << 'n';
}
};
std::cout << "~~~~ empty ~~~~n";
const auto empty=std::optional<int>{};
test_predicate("has_value",
has_value, empty);
test_predicate("is_positive",
is_positive, empty);
test_predicate("has_value_and_is_positive",
has_value_and_is_positive, empty);
test_predicate("is_positive_and_has_value",
is_positive_and_has_value, empty);
std::cout << "~~~~ positive ~~~~n";
const auto positive=std::optional<int>{5};
test_predicate("has_value",
has_value, positive);
test_predicate("is_positive",
is_positive, positive);
test_predicate("has_value_and_is_positive",
has_value_and_is_positive, positive);
test_predicate("is_positive_and_has_value",
is_positive_and_has_value, positive);
std::cout << "~~~~ negative ~~~~n";
const auto negative=std::optional<int>{-3};
test_predicate("has_value",
has_value, negative);
test_predicate("is_positive",
is_positive, negative);
test_predicate("has_value_and_is_positive",
has_value_and_is_positive, negative);
test_predicate("is_positive_and_has_value",
is_positive_and_has_value, negative);
}
/**
~~~~ empty ~~~~
has_value --> 0
is_positive !!! bad optional access
has_value_and_is_positive --> 0
is_positive_and_has_value !!! bad optional access
~~~~ positive ~~~~
has_value --> 1
is_positive --> 1
has_value_and_is_positive --> 1
is_positive_and_has_value --> 1
~~~~ negative ~~~~
has_value --> 1
is_positive --> 0
has_value_and_is_positive --> 0
is_positive_and_has_value --> 0
**/

最新更新