views:

819

answers:

3

Is there any way that you can combine predicates?

Lets say I have something like this:

class MatchBeginning : public binary_function<CStdString, CStdString, bool>
{   public:
       bool operator()(const CStdString &inputOne, const CStdString &inputTwo) const
 { return inputOne.substr(0, inputTwo.length()).compare(inputTwo) == 0; }
};

int main(int argc, char* argv[])
{
    CStdString myString("foo -b ar -t az"); 

    vector<CStdString> tokens;

    // splits the string every time it encounters a "-"
    split(myString, tokens, "-", true, true); 

    vector<CStdString>::iterator searchResult = find_if(tokens.begin(), tokens.end(), not1(bind2nd(MatchBeginning(), "-")));     

    return 0;
}

This works, but now I'd like to do something like:

searchResult = find_if(tokens.begin(), tokens.end(), bind2nd(MatchBeginning(), "-b") || not1(bind2nd(MatchBeginning(), "-")));

So I'd like to find the first string that starts with "-b" or the first string that does not start with "-". However, this gives me an error (binary '||' undefined).

Is there any way to do this?

+3  A: 

I can recommend boost.lambda for combining function-objects for such tasks. Although it is a bit heavyweight for such a simple problem. (edit) See the community wiki answer started by xhantt for a good example using STL.

(old, deprecated, answer) You can write your own utility for this, similar:

// here we define the combiner...
template<class Left, class Right>
class lazy_or_impl {
  Left m_left;
  Right m_right;
public:
  lazy_or_impl(Left const& left, Right const& right) : m_left(left), m_right(right) {}
  typename Left::result_type operator()(typename Left::argument_type const& a) const {
    return m_left(a) || m_right(a);
  }
};

// and a helper function which deduces the template arguments
// (thx to xtofl to point this out)
template<class Left, class Right>
lazy_or_impl<Left, Right> lazy_or(Left const& left, Right const& right) {
  return lazy_or_impl<Left, Right>(left, right);
}

and then use it: ... lazy_or(bind1st(...), bind1st(...)) ...

gimpf
You probably would need a shim function for that to compile, too: there is no template argument inference for classes.
xtofl
Thx for pointing this out, I edited the answer, and fixed some other part too...
gimpf
+3  A: 

If you want to compose predicates, the nicest way to write it is probably using the Boost Lambda or Boost Phoenix:

// Lambda way:
// Needs:
// #include <boost/lambda/lambda.hpp>
// #include <boost/lambda/bind.hpp>
{
    using namespace boost::lambda;
    foo_vec::const_iterator it
        = std::find_if(
                    tokens.begin(),
                    tokens.end(),
                    bind(MatchBeginning(), _1, "-b") || !bind(MatchBeginning(), _1, "-")
                    );
}
// Boost bind way:
// Needs:
// #include <boost/bind.hpp>
{
    foo_vec::const_iterator it
        = std::find_if(
                    tokens.begin(),
                    tokens.end(),
                    boost::bind(
                                std::logical_or<bool>(),
                                boost::bind(MatchBeginning(), _1, "-b"),
                                !boost::bind(MatchBeginning(), _1, "-") // ! overloaded in bind
                               )
                    );

For the Phoenix way one of the possibilities is to use phoenix lazy functions, and the solution could look similar to the one below:

// Requires:
// #include <boost/spirit/include/phoenix_core.hpp>
// #include <boost/spirit/include/phoenix_function.hpp>
// #include <boost/spirit/include/phoenix_operator.hpp>
namespace phx = boost::phoenix;

struct match_beginning_impl
{
    template <typename Arg1, typename Arg2>
    struct result
    {
        typedef bool type;
    };

    template <typename Arg1, typename Arg2>
    bool operator()(Arg1 arg1, Arg2 arg2) const
    {
        // Do stuff
    }
};
phx::function<match_beginning_impl> match_beginning;

using phx::arg_names::arg1;

foo_vec::const_iterator it
    = std::find_if(
                tokens.begin(),
                tokens.end(),
                match_beginning(arg1, "-b") || !match_beginning(arg1, "-")
                );

However to accomplish your task it probably makes more sense to employ different tools - for example: regular expressions (Boost Regex or Boost Xpressive). If you want to handle the command line options then use Boost Program Options.

Anonymous
I wish I could accept two answers for this question. In the end I found the non-library way the most interesting. Still, thanks for putting in the time to write out these snippets.
drby
+4  A: 

Well you have std::logical_or and std::compose2 that can do the job

find_if(tokens.begin(), tokens.end(), 
  compose2(logical_or<bool>(),
    bind2nd(MatchBeginning(), "-b"),
    bind2nd(MatchBeginning(), "-")
  ) 
);

but I think that boost::lambda and/or phoenix are more readable in the end, and are my recommended solution.

Credits should go to SGI documentation.

Ismael
I knew that I've forgotten the obvious compose. I just found logical_or, and just didn't remember it :-(
gimpf
The problem with compose2 is that it is not part of the current C++ standard.
Anonymous
Well, neither boost::lambda nor phoenix are part of the standard, yet.
Ismael
I know, I meant that it is pretty clear where to get the lambda and phoenix from, while for compose2 it's not that clear (some compilers have it, some not, etc).
Anonymous
But still think that boost::lambda is more readable and compact in the end, and if I can use boost the I will use it for sure.
Ismael
But sometimes in a compiler with limited resources it is easier to get compose2 working than the full boost::lambda.
Ismael
It is easier to hack your own version once you know how it is supposed to work.
Ismael
All the comments are valid - everything depends on the circumstances.
Anonymous