0

I have a full sample of what I want to accomplish below. Essentially I wanted my tree structure held in a memory map. The code below doesn't compile. I don't understand the (expansive) error; it's some kind of failed type conversion. How do I adjust this code to make it work?

#include "boost/interprocess/allocators/allocator.hpp"
#include "boost/interprocess/managed_mapped_file.hpp"

#include <map>
#include <memory>

template <typename TKey, typename TData, template<class> class TAllocator = std::allocator>
class Node : std::enable_shared_from_this<Node<TKey, TData, TAllocator>> {
    using TNode = Node<TKey, TData, TAllocator>;
    std::map<TKey, TNode, std::less<TKey>, TAllocator<std::pair<const TKey, TData>>> _children;
    TData _data;
public:
    Node() = default;
    explicit Node(const TAllocator<std::pair<const TKey, TData>>& allocator, const TData& data) : _children(allocator), _data(data) {}

    TData& at() { return _data; }
    const std::map<TKey, std::shared_ptr<TNode>>& children() { return _children; };

    void add(const TKey& key, const TData& data) {
        _children.emplace(key, TNode(_children.get_allocator(), data));
    }
};

template <typename T>
using TAlloc = boost::interprocess::allocator<T, boost::interprocess::managed_mapped_file::segment_manager>;
using TMapTrie = Node<std::string, std::shared_ptr<std::size_t>, TAlloc>;

int main() {
    boost::interprocess::managed_mapped_file file_vec(boost::interprocess::open_or_create, "/tmp/pfx_mmap.dat", 1 << 20);
    TAlloc<std::pair<const std::string, std::shared_ptr<std::size_t>>> allocator(file_vec.get_segment_manager());

    TMapTrie root(allocator, nullptr);
    root.add("abc", std::make_shared<std::size_t>(42));
    return 0;
}

You can compile it like this: gcc demo.cpp -lboost_system -std=c++11 -lstdc++. The compilation error:

cannot convert ‘std::allocator_traits<boost::interprocess::allocator<std::_Rb_tree_node<std::pair<const std::__cxx11::basic_string<char>, Node<std::__cxx11::basic_string<char>, std::shared_ptr<long unsigned int>, TAlloc> > >, boost::interprocess::segment_manager<char, boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family>, boost::interprocess::iset_index> > >::pointer {aka boost::interprocess::offset_ptr<std::_Rb_tree_node<std::pair<const std::__cxx11::basic_string<char>, Node<std::__cxx11::basic_string<char>, std::shared_ptr<long unsigned int>, TAlloc> > >, long int, long unsigned int, 0>}’ to ‘std::_Rb_tree<std::__cxx11::basic_string<char>, std::pair<const std::__cxx11::basic_string<char>, Node<std::__cxx11::basic_string<char>, std::shared_ptr<long unsigned int>, TAlloc> >, std::_Select1st<std::pair<const std::__cxx11::basic_string<char>, Node<std::__cxx11::basic_string<char>, std::shared_ptr<long unsigned int>, TAlloc> > >, std::less<std::__cxx11::basic_string<char> >, boost::interprocess::allocator<std::pair<const std::__cxx11::basic_string<char>, Node<std::__cxx11::basic_string<char>, std::shared_ptr<long unsigned int>, TAlloc> >, boost::interprocess::segment_manager<char, boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family>, boost::interprocess::iset_index> > >::_Link_type {aka std::_Rb_tree_node<std::pair<const std::__cxx11::basic_string<char>, Node<std::__cxx11::basic_string<char>, std::shared_ptr<long unsigned int>, TAlloc> > >*}’ in return
       { return _Alloc_traits::allocate(_M_get_Node_allocator(), 1); }
Brannon
  • 5,324
  • 4
  • 35
  • 83

1 Answers1

1

This should be a bug in the implementation of map in libstdc++. Its map implementation assumes that a allocator's pointer type (i.e. typename std::allocator_traits<some_allocator>::pointer) is actually a pointer.

This is not required by the standard, and allocator provided by boost::interprocess indeed uses an offset_ptr as the pointer type, which is an object instead of a real pointer.

The culprit code in bits/stl_tree.h:

  _Link_type
  _M_get_node()
  { return _Alloc_traits::allocate(_M_get_Node_allocator(), 1); }

where _Link_type is declared directly as a pointer:

  typedef _Rb_tree_node<_Val>*      _Link_type;

A correct implementation should use allocator_traits<...>::pointer to retrieve the pointer type.

With libc++, GCC and Clang both correctly accept the code (your original code has some irrelevant typo...), and its implementation does exact the above approach.

A minimal example to reproduce:

#include "boost/interprocess/allocators/allocator.hpp"
#include "boost/interprocess/managed_mapped_file.hpp"

#include <map>

template <typename T>
using TAlloc = boost::interprocess::allocator<T, boost::interprocess::managed_mapped_file::segment_manager>;

int main() {
    boost::interprocess::managed_mapped_file file_vec(boost::interprocess::open_or_create, "/tmp/pfx_mmap.dat", 1 << 20);
    TAlloc<std::pair<const int, int>> allocator(file_vec.get_segment_manager());

    std::map<int, int, std::less<int>, TAlloc<std::pair<const int, int>>> mp{allocator};
    mp.insert({1, 2});
}
llllllllll
  • 16,169
  • 4
  • 31
  • 54
  • do you know if there is an existing bug filed for this? – Brannon Dec 27 '18 at 15:52
  • @Brannon I haven't checked. I guess not, otherwise it should've been fixed. Probably you can file the bug. – llllllllll Dec 28 '18 at 06:25
  • @Brannon That's bizarre, the issue is literally raised five years ago but they still didn't fix... Maybe they think it's not that important anyway. In your case, you can use the `boost::interprocess` built-in `map`. See https://www.boost.org/doc/libs/1_69_0/doc/html/interprocess/quick_guide.html#interprocess.quick_guide.qg_interprocess_map – llllllllll Dec 28 '18 at 09:32