Search code examples
c++template-meta-programmingboost-fusion

Find the index of an element in a fusion map


I am struggling with a small piece of functionality I'm looking for.

I have a class which contains a fusion::map. I would like to use a variadic constructor to initialise the elements in that map.

I expect the easiest way to do this is to construct a fusion::vector from the constructor arguments, and then call for_each on the map, and setting each pair's value to its corresponding element in the vector.

However, in order to do this I need to calculate the index of the pair, based on its key type. (pair::first_type)

Can anyone help me?

Please see example code below:

#include <iostream>
#include <boost/fusion/container.hpp>
#include <boost/fusion/sequence.hpp>
#include <boost/fusion/mpl.hpp>
#include <boost/fusion/include/has_key.hpp>
#include <boost/fusion/include/algorithm.hpp>
#include <boost/mpl/transform.hpp>

namespace fusion = boost::fusion;
namespace mpl    = boost::mpl;

// given a field, returns a fusion pair of <field, field::type>
template<class field>
struct make_pair
{
    typedef typename fusion::result_of::make_pair<field, typename field::type>::type type;
};

// given a sequence of fields, returns a fusion map which maps field -> field::type
template<class... fields>
struct make_map
{
    typedef typename boost::fusion::vector<fields...> vector;
    typedef typename mpl::transform<vector, make_pair<mpl::_1>>::type pair_sequence;
    typedef typename fusion::result_of::as_map<pair_sequence>::type type;
};

// initialise each member of a map with the corresponding element in the vector
template<typename vector>
struct init
{
    init(vector& v) : _v(v) {}

    template <typename pair>
    void operator()(pair const& data) const
    {
       // TODO: use pair::first_type to find the index of this pair in the map, and set 
       // data.second to at_c<index>(_v);
    }
    vector& _v;
};

struct field1 { typedef int type; };
struct field2 { typedef int type; };

struct my_map
{
    template<typename... args>
    my_map(args... a)
    {
        typedef typename boost::fusion::vector<args...> vector;
        vector arg_vec(a...);

        fusion::for_each(_map, init<vector>(arg_vec));
    }
    typedef typename make_map<field1, field2>::type map;
    map _map;
};

struct print
{
    template <typename pair>
    void operator()(pair const& data) const
    {
        std::cout << data.second << " ";
    }
};


int main()
{
    my_map m(1, 2);
    fusion::for_each(m._map, print()); // should print '1 2'
    return 0;
}

Solution

  • Adding another answer to my own question, this time for the use-case where the number of arguments is less than or equal to the number of elements in the map.

    A working solution is below:

    #include <iostream>
    #include <boost/fusion/container.hpp>
    #include <boost/fusion/sequence.hpp>
    #include <boost/fusion/mpl.hpp>
    #include <boost/fusion/include/has_key.hpp>
    #include <boost/fusion/include/algorithm.hpp>
    #include <boost/mpl/transform.hpp>
    #include <boost/fusion/include/copy.hpp>
    #include <boost/fusion/include/begin.hpp>
    #include <boost/fusion/include/next.hpp>
    #include <boost/fusion/include/key_of.hpp>
    #include <boost/fusion/iterator/key_of.hpp>
    
    namespace fusion = boost::fusion;
    namespace mpl    = boost::mpl;
    
    // given a field, returns a fusion pair of <field, field::type>
    template<class field>
    struct make_pair
    {
        typedef typename fusion::result_of::make_pair<field, typename field::type>::type type;
    };
    
    // given a sequence of fields, returns a fusion map which maps field -> field::type
    template<class... fields>
    struct make_map
    {
        typedef typename fusion::result_of::as_map<typename mpl::transform<boost::fusion::vector<fields...>, make_pair<mpl::_1>>::type>::type type;
    };
    //-------------------------------------------------------------------------------------------
    
    // iterate through a fusion map to find the index of a given key
    template<typename iter, typename key_type, typename seek_type>
    struct key_index
    {
        typedef typename fusion::result_of::next<iter>::type        next_iter;
        typedef typename fusion::result_of::key_of<next_iter>::type next_key;
    
        enum { value = 1 + key_index<next_iter, next_key, seek_type>::value };
    };
    
    template<typename iter, typename seek_type>
    struct key_index<iter, seek_type, seek_type>
    {
        enum { value = 0 };
    };
    //-------------------------------------------------------------------------------------------
    
    // copy an element from a vector to a map, if the index in the vector exists
    template<typename map, typename vector, int index, bool in_vec>
    struct do_copy
    {
        template<typename T>
        void operator()(const vector& v, const T& dest)
        {
            const_cast<T&>(dest) = fusion::at_c<index>(v);
        }
    };
    
    template<typename map, typename vector, int index>
    struct do_copy<map, vector, index, false>
    {
        template<typename T>
        void operator()(const vector&, const T&)
        { }
    };
    //-------------------------------------------------------------------------------------------
    
    // initialise a map with the corresponding elements in a vector, vector may be smaller than the map
    template<typename vector, typename map>
    struct init
    {
        init(const vector& v) : _v(v) {}
    
        template <typename pair> void operator()(const pair& data) const
        {
            typedef typename fusion::result_of::begin<map>::type          begin_iter;
            typedef typename fusion::result_of::key_of<begin_iter>::type  key_type;
    
            enum { index  = key_index<begin_iter, key_type, typename pair::first_type>::value };
            enum { in_vec = fusion::result_of::size<vector>::type::value > index };
    
            do_copy<map, vector, index, in_vec>()(_v, data.second);
        }
    private:
        const vector& _v;
    };
    //-------------------------------------------------------------------------------------------
    
    struct field1 { typedef std::string type; };
    struct field2 { typedef int         type; };
    struct field3 { typedef double      type; };
    struct field4 { typedef std::string type; };
    struct field5 { typedef int         type; };
    struct field6 { typedef double      type; };
    
    struct my_map
    {
        template<typename... args>
        my_map(args... a)
        {
            typedef typename fusion::vector<args...> vector;
            fusion::for_each(_map, init<vector, map>(vector(a...)));
        }
        typedef typename make_map<field1, field2, field3, field4, field5, field6>::type map;
        map _map;
    };
    
    struct print
    {
        template <typename pair>
        void operator()(pair const& data) const
        {
            std::cout << data.second << " ";
        }
    };
    //-------------------------------------------------------------------------------------------
    
    int main()
    {
        my_map m("hello world", 5, 2.4);
        fusion::for_each(m._map, print());
        std::cout << std::endl;
        return 0;
    }