python152
python152

Reputation: 1951

composing multiple predicates for C++11 algorithms

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

Answers (2)

Caleth
Caleth

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

Deduplicator
Deduplicator

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

Related Questions