Search code examples
c++boost-mpltemplate-meta-programming

How should binary predicates be passed to a user-defined Boost.MPL algorithm?


Consider the following attempt at a Boost.MPL style metaprogramming version of std::any_of

    #include <iostream>                     // cout
    #include <type_traits>                  // is_base_of, is_pod
    #include <boost/mpl/apply.hpp>          // apply
    #include <boost/mpl/fold.hpp>           // fold
    #include <boost/mpl/lambda.hpp>         // lambda, _1, _2
    #include <boost/mpl/logical.hpp>        // and_, true_   
    #include <boost/mpl/vector.hpp>         // vector

    template
    <
            typename Sequence,
            typename Pred
    >
    struct all_of
    :
            boost::mpl::fold<
                    Sequence,
                    boost::mpl::true_,
                    boost::mpl::lambda<
                            boost::mpl::and_<
                                    boost::mpl::_1,
                                    boost::mpl::apply< Pred, boost::mpl::_2 >
                            >
                    >
            >
    {};

    typedef int P1; typedef char P2; typedef float P3;

    typedef boost::mpl::vector<
            P1, P2, P3
    > pod_types;

    struct B {}; struct D1: B {}; struct D2: B {}; struct D3: B {};

    typedef boost::mpl::vector<
            D1, D2, D3
    > derived_types;

    int main() 
    {
            std::cout << (std::is_pod<P1>::value) << '\n';  // true
            std::cout << (std::is_pod<P2>::value) << '\n';  // true
            std::cout << (std::is_pod<P3>::value) << '\n';  // true       

            std::cout << (
                    all_of<
                            pod_types, 
                            std::is_pod< boost::mpl::_1 >                        
                    >::type::value  // true
            ) << '\n';

            std::cout << (std::is_base_of<B, D1>::value) << '\n';   // true
            std::cout << (std::is_base_of<B, D2>::value) << '\n';   // true
            std::cout << (std::is_base_of<B, D3>::value) << '\n';   // true

            std::cout << (
                    all_of<
                            derived_types, 
                            std::is_base_of< B, boost::mpl::_1 >    
                    >::type::value  // false (but should be true)
            ) << '\n';

            return 0;
    }

This prints out: 1 1 1 1 1 1 1 0. I.e., the final call to all_of with std::is_base_of passed as a predicate generates false. Why does this not work? Apperently, the base class B does not get properly bound to the predicate. How should I pass a binary predicate? Some combination of mpl::lambda or mpl::bind?

UPDATE

Based on Luc Touraille's excellent answer, here is the lambda-free solution to my question, with as an added bonus the compile-time versions of none_of and any_of

    template<typename Sequence, typename Pred>
    struct all_of
    :
            std::is_same< typename 
                    boost::mpl::find_if<
                            Sequence,
                            boost::mpl::not_<Pred>
                    >::type, typename 
                    boost::mpl::end<Sequence>::type
            >
    {};

    template<typename Sequence, typename Pred>
    struct none_of
    :
            all_of< Sequence, boost::mpl::not_< Pred > >
    {};

    template<typename Sequence, typename Pred>
    struct any_of
    :
            boost::mpl::not_< none_of< Sequence, Pred > >
    {};

Solution

  • Here is a solution using find_if instead of fold:

    #include <type_traits>
    #include <boost/mpl/end.hpp>
    #include <boost/mpl/find_if.hpp>
    #include <boost/mpl/logical.hpp>
    
    template
    <
        typename Sequence,
        typename Pred
    >
    struct all_of
    :
        std::is_same< typename
            boost::mpl::end< Sequence >::type, typename
            boost::mpl::find_if<
                Sequence,
                boost::mpl::not_< Pred >
            >::type
        >
    {};
    

    If you want to stick to fold, you'll need to turn the predicate into a metafunction using lambda before invoking it, to protect the arguments:

    boost::mpl::apply< typename
        boost::mpl::lambda< Pred >::type,
        boost::mpl::_2
    >
    

    However, you'll note that this won't work either. I'm not sure why exactly, I think it is related to this discussion on the Boost mailing list. Apparently, there is an issue with the arity of apply which is greater than the one supported by lambda. Anyway, a simple workaround is to use apply1 instead of apply. Here is the full solution:

    template
    <
            typename Sequence,
            typename Pred
    >
    struct all_of
      : boost::mpl::fold<
            Sequence,
            boost::mpl::true_,
            boost::mpl::and_<
                boost::mpl::_1,
                boost::mpl::apply1< typename
                    boost::mpl::lambda< Pred >::type,
                    boost::mpl::_2
                >
            >
        >
    {};