Search code examples
templatesc++11lambdafunctional-programminggeneric-programming

How to create a generic function for combining binary predicates?


There are several related questions, e.g. algorithms with multiple (unary) predicates, or multiple predicates in general. But they do not seem to tackle the generic case that I'm aiming at, and the answers are often out-dated in that they refer to constructs that are deprecated in C++11. It might all boil down to this question related to type deduction in lambdas, but I'm not sure whether it can be considered as a duplicate.

I tried to create a function that combines two arbitrary binary predicates (in the meaning of "types that implement a two-argument operator() and return a bool"). My goal was to have the possibility to write code like this:

auto p0 = somePredicate();
auto p1 = someOtherPredicate();
auto p2 = evenMorePredicates();

auto combined = and(p0, or(p1, p2));

I know that something similar can be achieved with lambdas (as also suggested in the answer to one of the questions linked above), but this requires the argument types to be repeated in the lambda itself. I'd like to know how such an and or or function could really be implemented generically - particularly, for binary predicates with arbitrary argument types.

My basic approach (and a suggestive example) is the following:

#include <functional>
#include <iostream>

template <typename A, typename B, typename P0, typename P1>
std::function<bool(const A&, const B&)> and(
    const P0& p0, const P1& p1)
{
    return [p0, p1](const A& a, const B& b)
    {
        return p0(a, b) && p1(a, b);
    };
}

int main(int argc, char* argv[])
{
    auto p0 = [](const int& a, const int& b)
    {
        return a < 0 && b > 0;
    };
    auto p1 = [](const int& a, const int& b)
    {
        return a < 1 && b > 4;
    };

    // This is what I want:
    //auto combined = and(p0, p1);

    // This works, but ... 
    auto combined = and<int, int, std::function<bool(const int&, const int&)>, std::function<bool(const int&, const int&)>>(p0, p1);

    std::cout << "combined(-3,7) : " << combined(-3, 7) << std::endl;
    std::cout << "combined(-3,1) : " << combined(-3, 1) << std::endl;

    return 0;
}

In any case, the problem seems to be that the template parameters of the argument types can not be deduced at the call site. I tried variations thereof, based on other related questions here on stackoverflow, but no avail. My gut feeling is that there must be a (simple) solution for this, sneaking around some limitation of the type deduction system of lambdas, but I obviously didn't find the right path yet.


Solution

  • You could write a functor class to carry your expression:

    template <typename P0, typename P1>
    class AndFunctor {
    public:
        AndFunctor (P0 p0, P1 p1)
         : m_p0{std::move(p0)}, m_p1{p1}
        {}
    
        template <typename T, typename U>
        bool operator() (T&& t, U&& u) {
            return m_p0(t, u) && m_p1(t, u);   
        }
    
    private:
        P0 m_p0;
        P1 m_p1;
    };
    

    Then return an instance of this in your and function (and is a keyword, so this code renames it to and_p

    template <typename P0, typename P1>
    AndFunctor<P0,P1> and_p(P0 p0, P1 p1)
    {
        return { std::move(p0), std::move(p1) };
    }
    

    Then you just use it as you envisaged:

    auto combined = and_p(p0, p1);
    

    Live Demo