Reputation: 1951
I have read similar questions on stackoverflow such as this one. My particular use case seems a bit different: instead of applying all predicates all the time, I need to pick and combine them (&&, || ) in different ways, pending on user input: A best example I can think of is unix find
, where a user can:
# find all files on condition:
# size more than 100K, modified 7 days ago, from user group abc
find /path/to/somewhere -type f -size +100K -mtime +7 -group abc
Suppose I have the following predicates defined:
bool size_equal_to() { ... }
bool size_greater_to() { ... }
bool size_less_than() { ... }
bool type_is_file() { ... }
bool type_is_dir() { ... }
bool mtime_plus() { ... }
Composing such set of predicates in a lambda function to a container list (as suggested by the earlier question) is doable, but code structure is very messy. Any suggestions for better?
Upvotes: 0
Views: 222
Reputation: 62789
What you are describing is a tree of predicates, where the leaves are your atomic size_equal_to
etc, and the nodes are combiners (&&
or ||
).
You have something like
class Predicate
{
public:
virtual ~Predicate();
virtual bool operator()(Item &) = 0;
};
// child1 && child2 && ...
class Conjunction : public Predicate
{
std::vector<std::unique_ptr<Predicate>> children;
public:
bool operator()(Item & item)
{
for (auto & child : children)
if (!(*child)(item))
return false;
return true;
}
};
// child1 || child2 || ...
class Disjunction : public Predicate
{
std::vector<std::unique_ptr<Predicate>> children;
public:
bool operator()(Item & item)
{
for (auto & child : children)
if ((*child)(item))
return true;
return false;
}
};
// just inherit the operator() from a lambda
template <typename Lambda>
class Leaf : public Predicate, public Lambda
{
Leaf(Lambda lambda) : Lambda(lambda) {}
using Lambda::operator();
};
You would then have to write a mapping of your user input to these Predicate
objects, e.g. parse each argument, bind the parameters to your size_equal_to
or w/e, then add it to a Conjunction
.
If it's not a command line input, but instead a GUI, you probably want to draw it as a tree view, with "And" and "Or" nodes.
Upvotes: 0
Reputation: 45664
Well, first of all, if it's like find, you are IO-bounded, so you can just use flags. No need to get fancy.
But now let's assume you are actually computation-bounded, and testing the flags is significant:
Just write a template-function doing everything depending on if the flag in the template-argument is set, and then map from the flags in a variable, to flags in a template-argument, like this:
class Task {
... Whatever
public:
template <std::size_t N>
void operator()(std::integral_constant<std::size_t, N>) {
if (N & 1) test_0();
if (N & 2) test_1();
if (N & 4) test_2();
...
}
}
my_mux<1<<6>(n, the_task);
Of course, you need my_mux()
too:
#include <type_traits>
#include <stdexcept>
namespace detail {
template <std::size_t N, class F>
typename std::enable_if<!N>::type my_mux_helper(std::size_t n, F& f) {
n == 0 ? f(std::integral_constant<std::size_t, N>()) : throw std::invalid_argument("n must be smaller than N");
}
template <std::size_t N, class F>
typename std::enable_if<N>::type my_mux_helper(std::size_t n, F& f) {
n == N ? f(std::integral_constant<std::size_t, N>()) : my_mux_helper<N - 1>(n, f);
}
}
template <std::size_t N, class F>
void my_mux(std::size_t n, F f) {
detail::my_mux_helper<N - 1>(n, f);
}
See it online on coliru.
Upvotes: 1