Search code examples
c++boostmetaprogrammingfusion

generator for boost::fusion::map initialization with noncopyable value type


I'd like to create a base class which is able to contain N sockets. These are ZeroMQ sockets, and they are not copyable. In that case base class can generically implement shutdown behaviour on managed sockets and other service functions. Whereas derived classes can access the required socket using the tag type.

My idea was to use boost::fusion::map. Socket types are not default constructible thus I need to pass as parameters: reference to context and int identifying the socket type (ZMQ_{SOCKET_TYPE} which is int). Finally, there should be a generator function to create a socket_t to be placed in fusion::map. The idea is to use fusion::repetitive_view right now, as sockets have all the same initialization params.

Problem right now, is that: if I manually pass socket_t{context, int}, socket_t{context, int} instances to the fusion::map ctor everything just works fine. Using the generator results in compilation stating that socket_t is not copyable.

Here is the simplified code I am trying to compile:

#include <boost/mpl/transform.hpp>
#include <boost/fusion/include/pair.hpp>

#include <boost/fusion/include/map.hpp>
#include <boost/fusion/container/generation/make_vector.hpp>


using namespace std;
namespace mpl = boost::mpl;
namespace fus = boost::fusion;

struct context_t : boost::noncopyable
{};

struct socket_t : boost::noncopyable
{
  socket_t(context_t& context, int type) : context_{&context}, type_{type}
  {}

  socket_t(socket_t&& s) : context_{s.context_}, type_{s.type_}
  {}

  socket_t& operator=(socket_t&& s)
  {
    context_ = s.context_;
    type_ = s.type_;
    return *this;
  }

  void use_fields()const
  { // just to avoid warnings that fields are not used
    (void)context_;
    (void)type_;
  }

private:
  context_t* context_;
  int type_;
};

// we need a view of keys in conjunction with the context
template<class T>
auto infinite_context(fus::vector<T> v)
{
  return fus::repetitive_view<fus::vector<T>>{v};
}


template<class... Pairs>
struct base_type
{
  typedef fus::map<Pairs...> tagged_map;

  base_type() : context_{} //, sockets_{}
  {}

  context_t context_;
  //fus::map<Pairs...> sockets_;
};

struct tag1 {};
struct tag2 {};


int main(int argc, char* argv[])
{
  context_t c;

  typedef base_type<fus::pair<tag1, socket_t>, fus::pair<tag2, socket_t>> test_me_type;


  auto g = infinite_context(fus::make_vector([&c]()->socket_t{ return {c, 1}; }));

  test_me_type::tagged_map m1{socket_t{c, 1}, socket_t{c, 1}}; //OK
  //test_me_type::tagged_map m2{g}; //Error: access to deleted copy ctor!

  (void)g;
  (void)m1;
  //(void)m2;

  return 0;
}

IMO the issue is that, fusion::vector instance used in fusion::repetitive_view becomes not copyable... Thus no generation possible. How is it possible to generate and forward N socket_t instances to the fusion::map?


Solution

  • OK solved it.

    The key was to introduce a copyable proxy object which has implicit conversion operator to fusion::pair<TagType, socket_t>. The missing snippet is:

    struct convertible
    {
      convertible(context_t& c, int t)
        : c_{&c}
        , t_{t}
      {}
    
      template<class Tag>
      operator fus::pair<Tag, socket_t>()const
      {
        return socket_t{*c_, t_};
      }
    
    private:
      context_t* c_;
      int t_;
    };
    
    int main(int argc, char* argv[])
    {
      context_t c;
    
      typedef base_type<fus::pair<tag1, socket_t>, fus::pair<tag2, socket_t>> test_me_type;
    
      auto g = infinite_context(fus::make_vector(convertible{c, 1}));
    
      test_me_type::tagged_map m1{socket_t{c, 1}, socket_t{c, 1}}; //OK
      test_me_type::tagged_map m2{g}; //OK as well!
    
    
      (void)g;
      (void)m1;
      (void)m2;
    
      return 0;
    }