Search code examples
c++boosttemplate-meta-programmingvariadicboost-mpl

using boost mpl lambda with variadic template class


I am having a hard time understanding why the following simple program won't compile. I have a variadic template class (my_type below) which I want to use to transform an mpl vector. The following snippet leads to a compilation error "/boost/mpl/aux_/preprocessed/gcc/apply_wrap.hpp:38:19: 'apply' following the 'template' keyword does not refer to a template".

#include <boost/mpl/vector.hpp>
#include <boost/mpl/transform.hpp>

template <class... T>
struct my_type{};

using namespace boost::mpl;

using test_type = vector<int, double>;

// expected result is vector< my_type<int>, my_type<double> >
using result_type = transform< test_type, my_type<_> >::type; 

int main() {

}

Making my_type take a single template parameter works fine, but I would like to understand why the variadic version does not work. Thank you in advance!


Solution

  • The second argument expected by transform is a unary operation which you are not passing.

    In your case, my_type is not a metafunction that mpl expects since it is making use of a variadic parameter list.

    A metafunction in the simplest case exposes a type typedef or a ststic bool value. For eg:

    template <typename T>
    struct add_pointer {
        using type = T*;
    };
    

    Note how add_pointer converts the provided template parameter. This is an example of unary operation since it takes only one template parameter T.

    In your example my_type is purely a type and cannot be used as a metafunction or operation as it does not satisfy the criteria of the metafunction as required by the transform metafunction which is have a type field to indicate the transformed type.

    In some cases, a simple temmplate type is converted into a metafunction as explained in the Detailed Reasoning section below.

    Doc reference : http://www.boost.org/doc/libs/1_31_0/libs/mpl/doc/ref/Reference/transform.html

    Code:

    #include <boost/mpl/vector.hpp>
    #include <boost/mpl/transform.hpp>
    #include <boost/mpl/equal.hpp>
    
    #include <type_traits>
    #include <typeindex>
    #include <iostream>
    
    template <class... T>
    struct my_type{};
    
    using namespace boost::mpl;
    
    using test_type = vector<int, double>;
    
    template <typename T>
    struct add_my_type {
        using type = my_type<T>;
    };
    
    using result_type = typename transform< test_type, add_my_type<_1> >::type; 
    
    int main() {
        static_assert (equal<result_type, vector< my_type<int>, my_type<double> >>::value, "Nope!!");
    
        std::cout << typeid(result_type).name() << std::endl; 
    }
    

    LIVE DEMO

    Detailed Reason

    The reason explained above is pretty brief in should be enough to answer the question. But lets dig into the details as much as I can.

    DISCLAIMER: I am no expert in boost::mpl.

    As per the comment below by the OP, the original code works if we change my_type to:

    template <class T>
    struct my_type{};
    

    But that doesn't go well with what I mentioned previously i.e operation needs a type identifier. So, lets see, what mpl is doing under the hood:

    struct transform somewhat looks like:

    template<  
      typename Seq1 = mpl::na
    , typename Seq2OrOperation = mpl::na      
    , typename OperationOrInserter = mpl::na          
    , typename Inserter = mpl::na             
    >
    struct transform {
      boost::mpl::eval_if<
            boost::mpl::or_<
                boost::mpl::is_na<OperationOrInserter>, 
                boost::mpl::is_lambda_expression<my_type<mpl_::arg<1> > >,
                boost::mpl::not_<boost::mpl::is_sequence<my_type<mpl_::arg<1> > > >,
                mpl_::bool_<false>, 
                mpl_::bool_<false> 
            >,
            boost::mpl::transform1<
                boost::mpl::vector<int, double>, 
                my_type<mpl_::arg<1>>, 
                mpl_::na
            >, 
            boost::mpl::transform2<boost::mpl::vector<int, double>,
                my_type<mpl_::arg<1> >, 
                mpl_::na, mpl_::na> 
            >
    
    };
    

    The important part to look here is the is_lambda_expression metafunction which basically checks if your Operation meets the requirement of a metafunction.

    After applying some heavy macro and template machinery and specializations, the above check synthesises below struct:

    template<
          typename IsLE, typename Tag
        , template< typename P1 > class F
        , typename L1
        >
    struct le_result1
    {
        typedef F<
              typename L1::type
            > result_;
    
        typedef result_ type;
    };
    

    Here, F is your my_type and L1 is the placeholder . So, in essence the above struct is nothing but add_my_type which I had shown in my initial response.

    If I am correct till now, le_result1 is the operation that would be performed on your sequence.