Search code examples
c++boostdictionaryboost-fusionboost-phoenix

How can I make std::find_if and std::map work together using some boost library?


This question is inspired from another topic which poses this question:

Find the first value greater than user specified value from a map container

which can be solved in several ways. A typical C++03 solution defines a dedicated function (or functor) and pass it to std::find_if as third argument.

In C++11, one can avoid defining a dedicated function (or functor), and can instead make use of lambda as:

auto it = std:: find_if(m.begin(), mp.end(), 
                    [n](const std::pair<std::string, int> & x) -> bool
                       { return x.second > n; }
                   );

which is the accepted answer.

I'm still looking for a short and cool solution. If it were a vector, then I just learnt a cool solution which makes use of Boost.Phoenix and the solution becomes very concise (ideone demo):

std::vector<int> v = ...;
auto it = std::find_if(v.begin(), v.end(), arg1 > 4);

Here arg1 is a functor object defined in boost::phoenix::arg_names namespace, and the expression arg1>4 evaluates to another functor which then gets passed to std::find_if.

A quick test is (ideone),

std::cout<< (arg1 > 9)(v) << std::endl; //prints 0 if as v > 9 is false, else 1

//or store the functor first and then use it
const auto & f = arg1 > 9;
std::cout<<  f(v) << std::endl; //prints 0 if as v > 9 is false, else 1

My question is, I want to solve the map problem, in a similar way. Is there any such solution? Something like:

auto it = std::find_if(m.begin(),mp.end(), (???).second > n); //m is std::map

Or,

auto it = std::find_if(m.begin(),mp.end(), at<1>(arg1) > n);  //m is std::map

For it to work, the expression at<1>(arg1) > 2 has to evaluate to a functor which takes const std::pair & as argument. My gut feelings tells me that boost has this solution. :-)


Solution

  • Indeed, Boost.Fusion and Boost.Phoenix have exactly what you want built-in.

    If one includes the necessary header to adapt std::pair<> as a conforming Fusion sequence, then one can use Phoenix's lazy version of boost::fusion::at_c<> to access std::pair<>::first or std::pair<>::second (be sure to #include <boost/phoenix/fusion.hpp>).

    namespace phx = boost::phoenix;
    using phx::arg_names::arg1;
    
    auto it = std::find_if(m.begin(), m.end(), phx::at_c<1>(arg1) > n);
    

    EDIT: Full sample, tested with VC++ 2010 SP1 + Boost 1.47.0:

    #include <algorithm>
    #include <map>
    #include <string>
    #include <iostream>
    #include <boost/fusion/include/std_pair.hpp>
    #include <boost/phoenix/core.hpp>
    #include <boost/phoenix/operator.hpp>
    #include <boost/phoenix/fusion.hpp>
    
    int main()
    {
        namespace phx = boost::phoenix;
        using phx::arg_names::arg1;
    
        std::map<std::string, int> m;
        m["foo"]    = 1;
        m["bar"]    = 2;
        m["baz"]    = 3;
        m["qux"]    = 4;
        m["quux"]   = 5;
        m["corge"]  = 6;
        m["grault"] = 7;
        m["garply"] = 8;
        m["waldo"]  = 9;
        m["fred"]   = 10;
        m["plugh"]  = 11;
        m["xyzzy"]  = 12;
        m["thud"]   = 13;
    
        int const n = 6;
        auto it = std::find_if(m.cbegin(), m.cend(), phx::at_c<1>(arg1) > n);
        if (it != m.cend())
            std::cout << it->first << '\n'; // prints "fred"
    }