c++ - 存储指向 Boost MultiSet 索引的引用/指针

标签 c++ boost c++17 boost-multi-index

我正在尝试构建两个类 CollectionCollectionView ,它是 Boost.MultiIndex 之上的抽象。这个想法有点简单:给 CollectionView 一个 Collection 类型的实例,它会处理渲染。 Collection 添加了一个用于添加或删除项目的接口(interface),这反过来将向 CollectionView 发出信号,表明它需要做一些工作。
类的摘录如下所示:

template<class ItemType, typename ... Indexes>
struct Collection {
    using t_container = multi_index_container<ItemType, indexed_by<Indexes...>>;
    t_container itemSet;
}


template<class TCollectionType>
struct CollectionView {
    // Imagine that this view also keeps track of which
    // item in col corresponds to which item on the screen
    TCollectionType& col;
}
这个想法是 API 的用户可以完全控制可以索引哪些列,并且在编译时尽可能多地验证其正确性。
但是如果我们暂时想象一个 CollectionView 有一个 UI 让用户确定排序(从一个预定义的集合,因为它必须在编译时知道),那么实现它的方式就会有点问题。
  • 理想情况下,CollectionView 将通过知道如何到达正确的索引
  • 来遍历集合。
  • 迭代索引可能会随着用户输入而改变

  • 我必须以某种方式存储当前迭代索引是什么,并且确实还可以从 Collection 的 itemSet 中访问它。
    理想情况下,我会将以下属性添加到 Collection:
    struct CollectionView {
        t_index_type    currentIndex { col.itemSet.get<0>() }
        
        void Build() {
            for (const auto& item : currentIndex) {
                // Do stuff here
            }
        }
    }
    
    我不知道是什么,如果有的话,t_index_type可能是,因为每个索引(sequenced、ordered_non_unique 等)都有自己的类型。我可以强制用户实现 void Iterate(function<void(const ItemType&>) , 但这会给 API 的用户带来更多的代码。
    我在这里走到了死胡同,还是我的模板不够好?
    编辑:
    一种可能的解决方案是像这样使用模板:
    // Previous definitions omitted
    struct Collection {
        using t_callback_fn = std::function<void(const ItemType&)>;
        using t_iter_fn = std::function<void(t_callback_fn)>;
        void Iterate(t_callback_fn cb) const
        {
            iterFn(cb);
        }
        
        template<int N, bool reverse = false>
        void SetSortIndex()
        {
            iterFn = [this](t_callback_fn fn) {
                // The ideal would be to store this index as part of the class itself!
                auto& index = itemSet.template get<N>();
                if (reverse) {
                    for (auto it { index.rbegin() }; it != index.rend(); ++it)
                        fn(*it);
                    
                } else {
                    for (const auto &item : index)
                        fn(item);
                }
            };
        }
    }
    
    并使用容器:
    col.SetSortIndex<0, true>;
    col.Iterate([](const auto& it) { std::cout << it << '\n;};
    
    但这不是很好。

    最佳答案

    听起来您真的会得到 random_access_index 的服务。来自 Boost Multi Index (BMI)。
    您可以rearrange it以您希望的任何方式。因此,即使您想让用户手动重新排列事物,例如他们

  • 添加一个元素,它显示为最后一项,而与集合其余部分的顺序无关
  • 选择与 BMI 指数之一不完全匹配的自定义排序顺序

  • 那么你就可以。

    As an aside: note that you can also use BMI containers to merely index non-owned or shared elements. The implementation allows the element type to be T*, T const*, std::reference_wrapper, shared_ptr etc. without any other change to the functionality. Note that it uses generic pointer_traits for this so you can even use std::reference_wrapper<std::shared_ptr<T const*> > and it would still work.

    This is not related to the answer but does resonate with the concept of "external views" as you were contemplating.

    See e.g. https://www.boost.org/doc/libs/1_73_0/libs/multi_index/doc/reference/key_extraction.html#chained_pointers


    示范
    假设我们添加了 random_access透明地索引到您的容器中:
    template<class ItemType, typename ... Indexes>
    class Collection {
        template <typename> friend struct CollectionView;
        struct View;
        using t_container = bmi::multi_index_container<ItemType, 
          bmi::indexed_by<
            Indexes...,
            bmi::random_access<bmi::tag<View> > // additional!
          >
      >;
    
      private:
        t_container itemSet;
    };
    
    现在我们可以定义 View 来处理那个额外的索引:
    template<class TCollectionType>
    struct CollectionView {
        using MIC   = typename TCollectionType::t_container;
        using Tag   = typename TCollectionType::View;
        using Index = typename MIC::template index<Tag>::type;
    
        TCollectionType& col;
        Index& idx { col.itemSet.template get<Tag>() };
    
        // Imagine that this view also keeps track of which
        // item in col corresponds to which item on the screen
        //
        explicit CollectionView(TCollectionType& col) : col(col) {}
    
        auto begin() const { return idx.begin(); }
        auto end() const { return idx.end(); }
    };
    
    现在,我将添加一些排列函数,它们都通过一些现有索引进行排列:
    template <int n> void arrange_by() {
        idx.rearrange(col.itemSet.template get<n>().begin());
    }
    
    或通过用户指定的免费比较功能进行排列:
    template <typename Cmp> void arrange_by(Cmp cmp) {
        std::vector<std::reference_wrapper<T const> > v(idx.begin(), idx.end());
        std::sort(v.begin(), v.end(), cmp);
        idx.rearrange(v.begin());
    }
    
    Live On Coliru
    #include <boost/multi_index_container.hpp>
    #include <boost/multi_index/ordered_index.hpp>
    #include <boost/multi_index/random_access_index.hpp>
    #include <boost/multi_index/member.hpp>
    #include <iostream>
    #include <iomanip>
    
    namespace bmi = boost::multi_index;
    
    template<class ItemType, typename ... Indexes>
    class Collection {
        template <typename> friend struct CollectionView;
        struct View;
        using t_container = bmi::multi_index_container<ItemType, 
              bmi::indexed_by<
                  Indexes...,
                  bmi::random_access<bmi::tag<View> > // additional!
              >
           >;
    
      public:
        explicit Collection(std::initializer_list<ItemType> init) : itemSet(init) {}
    
        bool insert(ItemType const& item) {
            return itemSet.insert(item).second;
        }
    
        template <int index = 0, typename K>
        bool erase(K const& key) {
            return itemSet.template get<index>().erase(key);
        }
      private:
        t_container itemSet;
    };
    
    template<class TCollectionType>
    struct CollectionView {
        using MIC   = typename TCollectionType::t_container;
        using T     = typename MIC::value_type;
        using Tag   = typename TCollectionType::View;
        using Index = typename MIC::template index<Tag>::type;
    
        TCollectionType& col;
        Index& idx { col.itemSet.template get<Tag>() };
    
        // Imagine that this view also keeps track of which
        // item in col corresponds to which item on the screen
        //
        explicit CollectionView(TCollectionType& col) : col(col) {}
    
        template <int n> void arrange_by() {
            idx.rearrange(col.itemSet.template get<n>().begin());
        }
    
        template <typename Cmp> void arrange_by(Cmp cmp) {
            std::vector<std::reference_wrapper<T const> > v(idx.begin(), idx.end());
            std::stable_sort(v.begin(), v.end(), cmp);
            idx.rearrange(v.begin());
        }
    
        auto begin() const { return idx.begin(); }
        auto end() const { return idx.end(); }
    };
    
    /// example application
    struct Item {
        int id;
        std::string name;
    
        // some natural ordering just for demo
        bool operator<(Item const& other) const 
            { return std::tie(id, name) < std::tie(other.id, other.name); }
        bool operator>(Item const& other) const 
            { return std::tie(id, name) > std::tie(other.id, other.name); }
    };
    
    using Items = Collection<Item,
          bmi::ordered_unique<bmi::member<Item, int, &Item::id> >,
          bmi::ordered_unique<bmi::member<Item, std::string, &Item::name> > >;
    
    int main() {
        Items items {
            { 3, "three" },
            { 1, "one" },
            { 5, "five" },
            { 4, "four" },
            { 2, "two" },
            { 6, "six" },
        };
    
        CollectionView view(items);
    
        auto dump = [&view](auto caption) {
            std::cout << std::setw(12) << caption << ": ";
            for (auto const& [id, name] : view)
                std::cout << " { " << id << ", " << std::quoted(name) << " }";
            std::cout << "\n";
        };
    
        dump("default");
    
        view.arrange_by<1>(); // by name
        dump("by name");
    
        view.arrange_by<0>(); // by id
        dump("by id");
    
        view.arrange_by(std::less<Item>{});
        dump("std::less");
    
        view.arrange_by(std::greater<Item>{});
        dump("std::greater");
    
        auto funky = [](Item const& a, Item const& b) {
            return (a.name.length() - a.id) < (b.name.length() - b.id);
        };
        view.arrange_by(funky);
        dump("funky");
    
        // mutations are fine
        if (items.erase(1))
            std::cout << "Removed 1\n";
        dump("funky");
    
        if (items.insert(Item { 42, "answer" }))
            std::cout << "Inserted the answer (appears at end)\n";
        dump("funky");
    
        view.arrange_by<1>();
        dump("by name");
    }
    
    打印
         default:  { 3, "three" } { 1, "one" } { 5, "five" } { 4, "four" } { 2, "two" } { 6, "six" }
         by name:  { 5, "five" } { 4, "four" } { 1, "one" } { 6, "six" } { 3, "three" } { 2, "two" }
           by id:  { 1, "one" } { 2, "two" } { 3, "three" } { 4, "four" } { 5, "five" } { 6, "six" }
       std::less:  { 1, "one" } { 2, "two" } { 3, "three" } { 4, "four" } { 5, "five" } { 6, "six" }
    std::greater:  { 6, "six" } { 5, "five" } { 4, "four" } { 3, "three" } { 2, "two" } { 1, "one" }
           funky:  { 4, "four" } { 2, "two" } { 3, "three" } { 1, "one" } { 6, "six" } { 5, "five" }
    Removed 1
           funky:  { 4, "four" } { 2, "two" } { 3, "three" } { 6, "six" } { 5, "five" }
    Inserted the answer (appears at end)
           funky:  { 4, "four" } { 2, "two" } { 3, "three" } { 6, "six" } { 5, "five" } { 42, "answer" }
         by name:  { 42, "answer" } { 5, "five" } { 4, "four" } { 6, "six" } { 3, "three" } { 2, "two" }
    

    关于c++ - 存储指向 Boost MultiSet 索引的引用/指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62621979/

    相关文章:

    c++ - Boost 的数据驱动测试的连接运算符 `+` 损坏了第一列

    c++ - 如何在 on_entry 中使用 Boost.MSM is_flag_active?

    c++ - 为什么隐式转换不应用于模板化函数参数?

    c++ - std::experimental::optional<T> 实现:Constexpr 构造函数混淆

    c++ - 有没有办法将枚举类隐式转换为 std::byte?

    c++ - Linux gcc 选择尝试实例化函数应该在的模板

    c++ - 为什么VC2008认为这个类是抽象的?

    c++ - CUDA 在 Windows 上编译问题,Cmake 错误 : No CUDA toolset found

    c++ - 使用 Visual Studio 进行可重现的构建 - 目标文件差异

    c++ - 转换自 1601 年以来 100 ns 的数量以 boost C++ 中的 posix 时间