1

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.

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • You could just do `pred_and(p0, p1)`. Note that `and` is a reserved word. – TartanLlama May 17 '16 at 10:57
  • 1
    Deducing the parameter types for the returned closure isn't really possible in the generic case. What if your predicate was a generic lambda, or a function with multiple `operator()` overloads? – TartanLlama May 17 '16 at 10:59
  • 3
    can you use [c++14](http://coliru.stacked-crooked.com/a/d9329a693b80f4ed) ? – Piotr Skotnicki May 17 '16 at 11:03
  • @TartanLlama I noticed that something like `auto combined = and(p0,p1);` might be possible, but there (the same as with a lambda) the argument types have to be written down explictly. But of course, the argument of multiple possible overloads sounds convincing, and might be a reason of why it's impossible to achieve what I originally aimed at... – Marco13 May 17 '16 at 11:05
  • @PiotrSkotnicki No c++14 here yet but it seems to (partially?) already be part of c++11, and I'm currently having a closer look at this - thanks for this pointer! – Marco13 May 17 '16 at 11:07
  • 1
    @Marco13 You could adapt Piotr's solution to use functors instead of generic lambdas, like [this](http://coliru.stacked-crooked.com/a/56fa44f48bf226be). That'll work in C++11. – TartanLlama May 17 '16 at 11:09
  • @TartanLlama Actually, I tried something similar (inspired by [this answer](http://stackoverflow.com/a/545464/3182664)), but this still referred to the deprecated functional structures, and I didn't figure out that additional parameters for the operator (`T`, `U`) would do the trick to cover the argument types here. If someone posted this as a proper answer, I'd accept it. – Marco13 May 17 '16 at 12:46

1 Answers1

2

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

TartanLlama
  • 63,752
  • 13
  • 157
  • 193