How to use std::tuple types with boost::mpl algorithms?

4.5k views Asked by At

The boost::mpl algorithms seem not to be able to work on std::tuple types out of the box, e.g., the following does not compile (boost-1.46.0, g++ snapshot 2011-02-19):

#include <tuple>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/contains.hpp>

namespace mpl=boost::mpl;

typedef mpl::vector<int,float,bool> types;
static_assert(mpl::contains<types, float>::value, "vector contains bool");

typedef std::tuple<int,float,bool> types2;
// the following does not compile:
// error: no class template named ‘apply’ in ‘struct boost::mpl::contains_impl<boost::mpl::non_sequence_tag>’
static_assert(mpl::contains<types2, float>::value, "tuple contains bool");

What is the easiest way to make the boost::mpl algorithms work on std::tuple?

  • Does evtl. boost::fusion provide this functionality (as it does so for boost::tuple)?
  • If not, would it be possible to carry over the fusion implementation for boost::tuple to std::tuple easily?
  • If not either, do I really have to implement all the intrinsic metafunctions listed in the MPL documentation or which ones would be sufficient? (The docs only says "many of intrinsic metafunctions offer a default implementation that will work in majority of cases", but it is not clear which ones exactly. And some tests with just providing begin and end did not lead me anywhere).
3

There are 3 answers

8
KitsuneYMG On BEST ANSWER

Converting from std::tuple to boost types and back seems to be the easiest way

#include <iostream>
#include <tuple>
#include <type_traits>
#include <boost/mpl/if.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/vector.hpp>

namespace mpl = boost::mpl;

template<typename Sequence, typename T>
struct push_front;

template<template<typename...> class Sequence, typename T, typename ... Args>
struct push_front< Sequence<Args...>,T> {
  typedef Sequence<T, Args...> type;
};

template<template<typename...> class To, typename From> struct tuple_change;

template<template<typename...> class To, template<typename...> class From, typename ... Args>
struct tuple_change<To, From<Args...>>
{
  typedef To<Args...> type;
};

template<typename Sequence, size_t N>
struct at : std::tuple_element<N,Sequence> { };

template<typename Sequence>
struct empty;

template<template<typename...> class Sequence, typename ... Args>
struct empty<Sequence<Args...>> {
  typedef Sequence<> type;
};

template<
  size_t N,
  typename Sequence,
  template<typename> class Pred,
  typename ... Args >
struct while_impl
{
  typedef typename mpl::if_c<
    Pred<
        typename at<Sequence, sizeof...(Args) - N -1>::type
    >::value,
    typename push_front<
        typename while_impl<N-1, Sequence, Pred, Args...>::type, 
            typename at<Sequence,sizeof...(Args)-N-1>::type
    >::type,
    typename empty< Sequence > ::type
  >::type type;
};

template<
  typename Sequence,
  template<typename> class Pred,
  typename ... Args >
struct while_impl<-1, Sequence, Pred, Args...>
: empty<Sequence> {
};


template<
  typename Sequence,
  template<typename> class Pred>
struct while_;

template<
  template<typename...> class Sequence,
  template<typename> class Pred,
  typename ... Args >
struct while_< Sequence<Args...>, Pred >
{
  typedef typename while_impl<sizeof...(Args)-1, Sequence<Args...>, Pred, Args...>::type type;
};

template<typename T>
struct not_na : mpl::not_< std::is_same<mpl_::na, T> >
{ };

template<template<typename...> class To, typename From>
struct to_boost;

template<template<typename...> class To, typename...Args >
struct to_boost<To, std::tuple<Args...> > :
  tuple_change< mpl::vector, std::tuple<Args...> >
{ };

template< typename From >
struct to_std;

template<template<typename...> class From, typename...Args >
struct to_std< From<Args...> > :
   while_<typename tuple_change< std::tuple, From<Args...> >::type, not_na>
{ };

static_assert(
std::is_same<
    mpl::vector< char, int, bool>,
    typename to_boost<mpl::vector, std::tuple<char, int, bool> >::type
  >::value,
"tuple_change to boost failed");

static_assert(
  std::is_same<
    std::tuple< char, int, bool>,
    typename to_std< mpl::vector<char, int, bool> >::type
  >::value,
"tuple_change from boost failed");

int main(){ return 0;}

*tested with:
boost_1_46_0 and g++-4.5 on MacOSx
boost_1_45_0 and g++-4.5 on Ubuntu 10.10

0
Lars On

This is my version for converting between std::tuple and boost types, but as said in the comment above, conversion probably is not very compile-time efficient, i.e., will result in (unnecessary) long compile times. A solution that avoids conversion surely would be preferred...

#include <tuple>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/at.hpp>

namespace mpl=boost::mpl;

//_ 1. vector_size and vector_at for std::tuple and mpl sequences
template <typename SEQ> struct vector_size 
: mpl::size<SEQ>
{};

template <typename... TYPES> struct vector_size<std::tuple<TYPES...>> 
: std::tuple_size<std::tuple<TYPES...>>
{};

template <typename SEQ, size_t N> struct vector_at 
: mpl::at_c<SEQ, N>
{};

template <typename... TYPES, size_t N> struct vector_at<std::tuple<TYPES...>, N> 
: std::tuple_element<N, std::tuple<TYPES...>>
{};

//_ 2. convert
template <template <typename...> class COLLECT,
      typename SEQ, size_t N, typename... ARGS>
struct convert_helper 
  : convert_helper<COLLECT, SEQ, N-1, typename vector_at<SEQ, N-1>::type, ARGS...>
{};

template <template <typename...> class COLLECT, typename SEQ, typename... ARGS>
struct convert_helper<COLLECT, SEQ, 0, ARGS...> {
  typedef COLLECT<ARGS...> type;
};

template <template <typename...> class COLLECT, typename SEQ>
struct convert 
  : convert_helper<COLLECT, SEQ, vector_size<SEQ>::value>
{};

//_ 3. tests
typedef std::tuple<int, float, bool> types;
typedef mpl::vector<int, float, bool> types_v;

static_assert(std::is_same<convert<std::tuple, types_v>::type, types>::value, "boost2std works");
static_assert(std::is_same<convert<mpl::vector,types>::type, types_v>::value, "std2boost works");

int main() {}
0
capsterx On

If you wanted to not convert the std::tuple into a mpl type, you can overload the tag dispatching boost mpl uses:

#include <tuple>
#include <boost/mpl/sequence_tag.hpp>
#include <boost/mpl/pop_front_fwd.hpp>
#include <boost/mpl/push_front_fwd.hpp>
#include <boost/mpl/push_back_fwd.hpp>
#include <boost/mpl/front_fwd.hpp>
#include <boost/mpl/empty_fwd.hpp>
#include <boost/mpl/size_fwd.hpp>
#include <boost/mpl/at_fwd.hpp>
#include <boost/mpl/back_fwd.hpp>
#include <boost/mpl/clear_fwd.hpp>
#include <boost/mpl/pop_back_fwd.hpp>
#include <boost/mpl/iterator_tags.hpp>
#include <boost/mpl/next_prior.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/mpl/begin_end_fwd.hpp>


namespace boost { namespace mpl {
  namespace aux { struct std_tuple; }

  template<class ... Args>
  struct sequence_tag<std::tuple<Args...> >
  {
    typedef aux::std_tuple type;
  };

  template<>
  struct front_impl< aux::std_tuple >
  {
    template< typename Tuple > struct apply
        : std::tuple_element<0, Tuple>
    {
    };
  };

  template<>
  struct empty_impl< aux::std_tuple >
  {
      template< typename Tuple > struct apply
          : std::integral_constant<bool, std::tuple_size<Tuple>::value == 0>
      {
      };
  };

  template<>
  struct pop_front_impl< aux::std_tuple >
  {
    template< typename Tuple > struct apply;

    template< class First, class ... Types > struct apply<std::tuple<First, Types...>>
    {
      typedef std::tuple<Types...> type;
    };
  };

  template<>
  struct push_front_impl< aux::std_tuple >
  {
    template< typename Tuple, typename T > struct apply;

    template< typename T, typename ... Args >
    struct apply<std::tuple<Args...>, T>
    {
        typedef std::tuple<T, Args...> type;
    };
  };

  template<>
  struct push_back_impl< aux::std_tuple >
  {
    template< typename Tuple, typename T > struct apply;

    template< typename T, typename ... Args  >
    struct apply<std::tuple<Args...>, T>
    {
      typedef std::tuple<Args..., T> type;
    };
  };


  template<>
  struct size_impl< aux::std_tuple >
  {
    template< typename Tuple > struct apply
        : std::tuple_size<Tuple>
    {
    };
  };

  template<>
  struct at_impl< aux::std_tuple >
  {
    template< typename Tuple, typename N > struct apply
        : std::tuple_element<N::value, Tuple>
    {
    };
  };

  template<>
  struct back_impl< aux::std_tuple >
  {
    template< typename Tuple > struct apply
        : std::tuple_element<std::tuple_size<Tuple>::value - 1, Tuple>
    {
    };
  };

  template<>
  struct clear_impl< aux::std_tuple >
  {
    template< typename Tuple > struct apply
    {
      typedef std::tuple<> type;
    };
  };

  template<>
  struct pop_back_impl< aux::std_tuple >
  {
    template<int ...> struct tuple_seq {};
    template<int N, int ...S> struct tuple_gens : tuple_gens<N-1, N-1, S...> {};
    template<int ...S> struct tuple_gens<0, S...>{ typedef tuple_seq<S...> type; };

    template < class Tuple, class Index> struct apply_impl;
    template < class Tuple, int ... S> struct apply_impl<Tuple, tuple_seq<S...>>
    {
      typedef std::tuple<typename std::tuple_element<S, Tuple>::type...> type;
    };

    template< typename Tuple > struct apply : apply_impl<Tuple, typename tuple_gens<std::tuple_size<Tuple>::value - 1>::type> { };
  };

template< class ... Args >
struct tuple_iter;

  template< class ... Args >
  struct tuple_iter<std::tuple<Args...>>
  {
    typedef aux::std_tuple tag;
    typedef forward_iterator_tag category;
  };

template<>
struct begin_impl< aux::std_tuple >
{
  template< class Tuple > struct apply
  {
    typedef tuple_iter<Tuple> type;
  };
};

template<>
struct end_impl< aux::std_tuple >
{
  template< typename > struct apply
  {
    typedef tuple_iter<std::tuple<>> type;
  };
};

template< typename First, class ... Args >
struct deref< tuple_iter<std::tuple<First, Args...> > >
{
  typedef First type;
};

template< typename First, class ... Args >
struct next< tuple_iter<std::tuple<First, Args...>> >
{
  typedef tuple_iter< std::tuple<Args...> > type;
};

} }

And the associated test:

#include <boost/mpl/pop_front.hpp>
#include <boost/mpl/push_front.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/back.hpp>
#include <boost/mpl/clear.hpp>
#include <boost/mpl/pop_back.hpp>
#include <boost/mpl/contains.hpp>


#include <boost/mpl/aux_/test.hpp>
MPL_TEST_CASE()
{
  typedef std::tuple<int, char, bool> Tuple;
  MPL_ASSERT((is_same<front<Tuple>::type, int>));
  MPL_ASSERT_RELATION( size<Tuple>::type::value, ==, 3 );
  MPL_ASSERT(( is_same< pop_front<Tuple>::type, std::tuple<char, bool> > ));
  MPL_ASSERT(( is_same< push_front<Tuple, unsigned>::type, std::tuple<unsigned, int, char, bool> > ));
  MPL_ASSERT(( is_same< push_back<Tuple, unsigned>::type, std::tuple<int, char, bool, unsigned> > ));
  MPL_ASSERT_RELATION( empty<Tuple>::type::value, ==, false );
  MPL_ASSERT(( is_same< at_c<Tuple, 0>::type, int > ));
  MPL_ASSERT(( is_same< at_c<Tuple, 1>::type, char > ));
  MPL_ASSERT(( is_same< back<Tuple>::type, bool > ));
  MPL_ASSERT(( is_same< clear<Tuple>::type, std::tuple<> > ));
  MPL_ASSERT(( is_same< pop_back<Tuple>::type, std::tuple<int, char> > ));
  MPL_ASSERT(( contains<Tuple, int> ));
  MPL_ASSERT(( contains<Tuple, char> ));
  MPL_ASSERT(( contains<Tuple, bool> ));
  MPL_ASSERT_NOT(( contains<Tuple, unsigned> ));

}

I tested this with gcc 4.7.2 and clang 3.2. It should contain everything you need to use anything in the mpl (actually a litte more than it needs). You can think of a tuple as a mpl::list (forward iterator compile type). So, to make it play nice you should implmenet what boost::mpl::list does. A quick look into the boost/mpl/list/aux_ directory: begin_end.hpp empty.hpp iterator.hpp pop_front.hpp push_back.hpp size.hpp clear.hpp front.hpp push_front.hpp tag.hpp are the relevant files that need to be implemented.