根据键合并两个 Boost Fusion 映射

Merge two Boost Fusion maps based on keys

我有两个 boost::fusion::map 我想以某种方式合并。对于这两个地图,我想生成第三个地图,它具有两个地图中都存在的所有键,如果两个地图都存在,则添加值。例如:

#include <boost/fusion/container/map.hpp>
namespace bfn = boost::fusion;
using namespace boost::fusion;

struct x{};
struct y{};
struct z{};

int main(){

    auto m = make_map<x, y>(2, 4.3);
    auto n = make_map<x>(2.);
    auto l = accumulate_merge(m, n); // how this function should look like?
}

之后 l 将等同于 make_map<x, y>(2 + 2., 4.3).

我不知道从哪里开始。我尝试从 join 开始(并消除重复项,但我很快就变得复杂了)。

Boost Fusion 中是否有可以帮助我的工具?

(有很多微妙之处,比如如果对于同一个键,两个相应的类型不同——但仍然可以添加——。但是任何第一个版本都会有所帮助)。

我设法用 boost::fusion::fold 两次想出了这个版本。我不确定它是否是(编译时)最优的。

基本上,我先遍历第一张地图。如果键不在第二个映射中,则将元素推送到结果。如果键在第二个映射中,则添加元素和结果(无论将什么类型推送到结果)。

最后,我在第二张地图上进行了迭代。如果密钥在第一张地图中,我将忽略它(因为它是在第一遍中添加的)。如果键不在第二个中,则将元素推送到结果中。

(结果是第一个map优先于key类型的排序)

有一些未解决的问题或疑问:

1) 有更多 efficient/compact 方法可以做到这一点

2) 我不确定 forward 的用途,基本上我到处都用它(以防万一)

3) 不确定函数 returns 中的 autodecltype(auto)

4) 这些函数对 SFINAE 不友好,我可以添加守卫来生成软错误。 (例如,如果无法添加值)。

5) 结果没有任何自然顺序,我不知道这是算法问题还是 fold 问题,或者因为 map 没有指定 push_back 的顺序(之后都是地图)。

Comments are welcome.

现在代码:

namespace boost{
namespace fusion{
namespace detail{
template<class Map2>
struct merge1{
    Map2 m2_;
    template<typename MapOut, typename Pair1>
    auto operator()(MapOut&& mo, Pair1&& p1) const{
        return conditional_push_back(std::forward<MapOut>(mo), std::forward<Pair1>(p1), has_key<typename std::decay_t<Pair1>::first_type>(m2_));
    }
    template<typename MapOut, typename Pair1> 
    auto conditional_push_back(MapOut&& mo, Pair1&& p1, mpl_::bool_<false>) const{
        return push_back(std::forward<MapOut>(mo), std::forward<Pair1>(p1));
    }
    template<typename MapOut, typename Pair1>
    auto conditional_push_back(MapOut&& mo, Pair1&& p1, mpl_::bool_<true>) const{
        return push_back(std::forward<MapOut>(mo), make_pair<typename std::decay_t<Pair1>::first_type>(p1.second + at_key<typename std::decay_t<Pair1>::first_type>(m2_)));
    }
};
template<class Map2>
merge1<Map2> make_merge1(Map2&& m2){return {std::forward<Map2>(m2)};}

template<class Map1>
struct merge2{
    Map1 m1_;
    template<typename MapOut, typename Pair2>
    auto operator()(MapOut&& mo, Pair2&& p2) const{
        return conditional_push_back(std::forward<MapOut>(mo), std::forward<Pair2>(p2), has_key<typename std::decay_t<Pair2>::first_type>(m1_));
    }
    template<typename MapOut, typename Pair2>
    auto conditional_push_back(MapOut&& mo, Pair2&& p2, mpl_::bool_<false>) const{
        return push_back(std::forward<MapOut>(mo), std::forward<Pair2>(p2));
    }
    template<typename MapOut, typename Pair2>
    auto conditional_push_back(MapOut&& mo, Pair2&&   , mpl_::bool_<true>) const{
        return mo;
    }
};
template<class Map1> 
merge2<Map1> make_merge2(Map1&& m){return {std::forward<Map1>(m)};}
}

template<class Map1, class Map2>
inline auto accumulate_merge(Map1&& m1, Map2&& m2){
    return 
        as_map( // not completely sure this is a good idea
            fold( // second map2 is checked for unpaired elements
                std::forward<Map1>(m1), 
                fold( // first map1 takes the lead
                    std::forward<Map2>(m2), 
                    make_map<>(), 
                    detail::make_merge1(std::forward<Map1>(m1))
                ), 
                detail::make_merge2(std::forward<Map2>(m2))
            )
        );
}
}}

namespace bfn = boost::fusion;
using namespace boost::fusion;

struct x{};
struct y{};
struct z{};

int main(){

    auto m = make_map<x, y   >(2, 4.3    );
    auto n = make_map<   y, z>(   2  , 8.);
    auto l = accumulate_merge(m, n);
    assert( at_key<x>(l) == at_key<x>(m) );
    assert( at_key<y>(l) == at_key<y>(m) + at_key<y>(n) );
    assert( typeid(at_key<y>(l)) == typeid(at_key<y>(m) + at_key<y>(n)) );
    assert( at_key<z>(l) == at_key<z>(n) );
}